Compare commits
No commits in common. "ada9d99644fd855df17ad15b1ab7f2c99ac1afcc" and "369202476ee7f3f8a7964311ca3240d8b76af72c" have entirely different histories.
ada9d99644
...
369202476e
27 changed files with 53 additions and 280 deletions
|
@ -6,4 +6,5 @@ UID=1000
|
||||||
GID=1000
|
GID=1000
|
||||||
|
|
||||||
DATABASE_ENGINE= postgresql_psycopg2
|
DATABASE_ENGINE= postgresql_psycopg2
|
||||||
|
DATABASE_HOST=db
|
||||||
DATABASE_PORT=5432
|
DATABASE_PORT=5432
|
||||||
|
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
@ -5,5 +5,5 @@
|
||||||
<option name="enabledOnSave" value="true" />
|
<option name="enabledOnSave" value="true" />
|
||||||
<option name="sdkName" value="Python 3.13 (movie-night-py)" />
|
<option name="sdkName" value="Python 3.13 (movie-night-py)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.12.7 Docker (<none>:<none>)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (movie-night-py)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
2
.idea/movie-night-py.iml
generated
2
.idea/movie-night-py.iml
generated
|
@ -16,7 +16,7 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Remote Python 3.12.7 Docker (<none>:<none>)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.13 (movie-night-py)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PyDocumentationSettings">
|
<component name="PyDocumentationSettings">
|
||||||
|
|
|
@ -35,4 +35,4 @@ USER web
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "movienight.wsgi:application"]
|
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "djangodocker.wsgi:application"]
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
Movie Night API
|
|
||||||
=
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- Docker
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
ASGI config for movienight project.
|
ASGI config for djangodocker project.
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@ import os
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangodocker.settings')
|
||||||
|
|
||||||
application = get_asgi_application()
|
application = get_asgi_application()
|
|
@ -28,7 +28,6 @@ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
|
||||||
DEBUG = bool(os.environ.get("DEBUG", default=0))
|
DEBUG = bool(os.environ.get("DEBUG", default=0))
|
||||||
|
|
||||||
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1").split(",")
|
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1").split(",")
|
||||||
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -43,14 +42,11 @@ INSTALLED_APPS = [
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
"knox",
|
"knox",
|
||||||
"movie_manager",
|
|
||||||
"corsheaders",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"corsheaders.middleware.CorsMiddleware",
|
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
@ -58,7 +54,7 @@ MIDDLEWARE = [
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "movienight.urls"
|
ROOT_URLCONF = "djangodocker.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
|
@ -76,7 +72,7 @@ TEMPLATES = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = "movienight.wsgi.application"
|
WSGI_APPLICATION = "djangodocker.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
URL configuration for movienight project.
|
URL configuration for djangodocker project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import knox
|
import knox
|
||||||
|
@ -11,14 +11,11 @@ from django.conf import settings
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from users import views as user_views
|
from users import views as user_views
|
||||||
from movie_manager import views as movie_views
|
|
||||||
from rest_framework.authtoken.views import obtain_auth_token
|
from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r"api/users", user_views.UserViewSet)
|
router.register(r"api/users", user_views.UserViewSet)
|
||||||
router.register(r"api/groups", user_views.GroupViewSet)
|
router.register(r"api/groups", user_views.GroupViewSet)
|
||||||
router.register(r"api/movies", movie_views.MovieViewset)
|
|
||||||
router.register(r"api/lists", movie_views.MovieListViewset)
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include(router.urls)),
|
path("", include(router.urls)),
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
WSGI config for movienight project.
|
WSGI config for djangodocker project.
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@ import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangodocker.settings')
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
|
@ -1,7 +1,6 @@
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
container_name: movienight-db
|
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${DATABASE_NAME}
|
POSTGRES_DB: ${DATABASE_NAME}
|
||||||
POSTGRES_USER: ${DATABASE_USERNAME}
|
POSTGRES_USER: ${DATABASE_USERNAME}
|
||||||
|
@ -11,23 +10,16 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- movienight_data:/var/lib/postgresql/data
|
- djangodocker_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready", "-d", "$DATABASE_NAME"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 60s
|
|
||||||
retries: 5
|
|
||||||
start_period: 80s
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build: .
|
build: .
|
||||||
user: ${UID}:${GID}
|
user: ${UID}:${GID}
|
||||||
container_name: movienight-api
|
container_name: djangodocker-api
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
- db
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
environment:
|
||||||
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
|
||||||
DEBUG: ${DEBUG}
|
DEBUG: ${DEBUG}
|
||||||
|
@ -48,4 +40,4 @@ services:
|
||||||
- /etc/group:/etc/group:ro
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
movienight_data:
|
djangodocker_data:
|
||||||
|
|
27
firstRun.sh
27
firstRun.sh
|
@ -5,16 +5,16 @@ set -e
|
||||||
read -p "What is the project's name? " -r PROJECT_NAME
|
read -p "What is the project's name? " -r PROJECT_NAME
|
||||||
if [ -z "$PROJECT_NAME" ]
|
if [ -z "$PROJECT_NAME" ]
|
||||||
then
|
then
|
||||||
PROJECT_NAME="movienight"
|
PROJECT_NAME="djangodocker"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "===== UPDATING PROJECT NAME ====="
|
echo "===== UPDATING PROJECT NAME ====="
|
||||||
git ls-files | xargs sed -i "s/movienight/${PROJECT_NAME}/g"
|
git ls-files | xargs sed -i "s/djangodocker/${PROJECT_NAME}/g"
|
||||||
echo "Done!"
|
echo "Done!"
|
||||||
|
|
||||||
echo "===== UPDATING ENVIRONMENT ====="
|
echo "===== UPDATING ENVIRONMENT ====="
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
sed -i "s/movienight/${PROJECT_NAME}/g" ./.env
|
sed -i "s/djangodocker/${PROJECT_NAME}/g" ./.env
|
||||||
|
|
||||||
# SET DATABASE USERNAME
|
# SET DATABASE USERNAME
|
||||||
read -p "Enter a username for the database: " -r DATABASE_USERNAME
|
read -p "Enter a username for the database: " -r DATABASE_USERNAME
|
||||||
|
@ -30,10 +30,8 @@ then
|
||||||
DATABASE_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 15)
|
DATABASE_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 15)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# WRITE VARIABLES TO .ENV FILE
|
|
||||||
SECRET_KEY=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 50)
|
SECRET_KEY=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 50)
|
||||||
{
|
{
|
||||||
echo "DATABASE_HOST=${PROJECT_NAME}-db"
|
|
||||||
echo "DATABASE_NAME=${PROJECT_NAME}"
|
echo "DATABASE_NAME=${PROJECT_NAME}"
|
||||||
echo "DATABASE_USERNAME=${DATABASE_USERNAME}"
|
echo "DATABASE_USERNAME=${DATABASE_USERNAME}"
|
||||||
echo "DATABASE_PASSWORD=${DATABASE_PASSWORD}"
|
echo "DATABASE_PASSWORD=${DATABASE_PASSWORD}"
|
||||||
|
@ -41,26 +39,15 @@ SECRET_KEY=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 50)
|
||||||
echo "DJANGO_SECRET_KEY=${SECRET_KEY}"
|
echo "DJANGO_SECRET_KEY=${SECRET_KEY}"
|
||||||
} >> .env
|
} >> .env
|
||||||
|
|
||||||
# RENAME PROJECT DIRECTORY
|
mv djangodocker "$PROJECT_NAME"
|
||||||
if [ "$PROJECT_NAME" != "movienight" ]; then
|
|
||||||
mv movienight "$PROJECT_NAME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "===== STARTING DOCKER ====="
|
echo "===== STARTING DOCKER ====="
|
||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
|
|
||||||
echo "===== MIGRATING DATABASE ====="
|
echo "===== MIGRATING DATABASE ====="
|
||||||
docker exec -ti "${PROJECT_NAME}-api" ./manage.py migrate
|
docker exec -ti "${PROJECT_NAME}-api" ./manage.py migrate
|
||||||
|
|
||||||
echo "===== CREATING SUPERUSER ====="
|
echo "===== CREATE SUPERUSER ====="
|
||||||
docker exec -ti "${PROJECT_NAME}-api" ./manage.py createsuperuser
|
docker exec -ti "${PROJECT_NAME}-api" ./manage.py createsuperuser
|
||||||
|
|
||||||
echo "===== COLLECTING STATIC FILES ====="
|
|
||||||
docker exec -ti "${PROJECT_NAME}-api" ./manage.py collectstatic
|
|
||||||
|
|
||||||
echo "===== RESTARTING DOCKER CONTAINERS ====="
|
|
||||||
docker compose restart
|
|
||||||
|
|
||||||
echo "Success! Go to http://localhost:8000 to see API documentation."
|
echo "Success! Go to http://localhost:8000 to see API documentation."
|
||||||
|
|
||||||
git remote remove origin
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangodocker.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class MovieManagerConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'movie_manager'
|
|
|
@ -1,48 +0,0 @@
|
||||||
# Generated by Django 5.1.4 on 2025-03-31 04:04
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Movie',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=100)),
|
|
||||||
('imdb_id', models.CharField(max_length=100)),
|
|
||||||
('year', models.IntegerField()),
|
|
||||||
('critic_score', models.CharField(max_length=500)),
|
|
||||||
('genre', models.CharField(max_length=100)),
|
|
||||||
('director', models.CharField(max_length=500)),
|
|
||||||
('actors', models.CharField(max_length=500)),
|
|
||||||
('plot', models.CharField(max_length=500)),
|
|
||||||
('poster', models.CharField(max_length=500)),
|
|
||||||
('last_watched', models.DateTimeField()),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='MovieList',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100)),
|
|
||||||
('public', models.BooleanField(default=False)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
|
||||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Generated by Django 5.1.4 on 2025-04-07 05:02
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('movie_manager', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='movie',
|
|
||||||
options={'ordering': ['title']},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='movielist',
|
|
||||||
options={'ordering': ['name']},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='movielist',
|
|
||||||
name='movies',
|
|
||||||
field=models.ManyToManyField(to='movie_manager.movie'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,40 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
class Movie(models.Model):
|
|
||||||
class Meta:
|
|
||||||
ordering = ["title"]
|
|
||||||
|
|
||||||
title = models.CharField(max_length=100)
|
|
||||||
imdb_id = models.CharField(max_length=100)
|
|
||||||
year = models.IntegerField()
|
|
||||||
critic_score = models.CharField(max_length=500)
|
|
||||||
genre = models.CharField(max_length=100)
|
|
||||||
director = models.CharField(max_length=500)
|
|
||||||
actors = models.CharField(max_length=500)
|
|
||||||
plot = models.CharField(max_length=500)
|
|
||||||
poster = models.CharField(max_length=500)
|
|
||||||
last_watched = models.DateTimeField()
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
|
|
||||||
class MovieList(models.Model):
|
|
||||||
class Meta:
|
|
||||||
ordering = ["name"]
|
|
||||||
|
|
||||||
name = models.CharField(max_length=100)
|
|
||||||
public = models.BooleanField(default=False)
|
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
movies = models.ManyToManyField(Movie)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
|
@ -1,21 +0,0 @@
|
||||||
from itertools import count
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from movie_manager.models import Movie, MovieList
|
|
||||||
|
|
||||||
class MovieSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Movie
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
|
|
||||||
class MovieListSerializer(serializers.ModelSerializer):
|
|
||||||
movie_count = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = MovieList
|
|
||||||
fields = ["id","name","owner","public", "movie_count"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_movie_count(self, obj):
|
|
||||||
return len(obj.movies.all())
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,48 +0,0 @@
|
||||||
from django.http import HttpResponse, JsonResponse
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from rest_framework import permissions, viewsets
|
|
||||||
from knox.auth import TokenAuthentication
|
|
||||||
from rest_framework.exceptions import NotFound
|
|
||||||
|
|
||||||
from movie_manager.models import Movie, MovieList
|
|
||||||
from movie_manager.serializers import MovieListSerializer, MovieSerializer
|
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
class MovieViewset(viewsets.ModelViewSet):
|
|
||||||
fields = '__all__'
|
|
||||||
queryset = Movie.objects.all().order_by("title")
|
|
||||||
authentication_classes = [TokenAuthentication]
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
serializer_class = MovieSerializer
|
|
||||||
|
|
||||||
class MovieListViewset(viewsets.ModelViewSet):
|
|
||||||
fields = '__all__'
|
|
||||||
queryset = MovieList.objects.all().order_by("name")
|
|
||||||
authentication_classes = [TokenAuthentication]
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
|
||||||
|
|
||||||
serializer_class = MovieListSerializer
|
|
||||||
|
|
||||||
def update(self, request, pk=None, *args, **kwargs):
|
|
||||||
movie_list = MovieList.objects.get(pk=pk)
|
|
||||||
movie_list.name = request.data.get('name')
|
|
||||||
movie_list.owner = User.objects.get(pk=request.data.get("owner"))
|
|
||||||
|
|
||||||
if request.data.get('movies'):
|
|
||||||
movie_ids = request.data.get('movies')
|
|
||||||
for movie_id in movie_ids:
|
|
||||||
try:
|
|
||||||
movie = Movie.objects.get(pk=movie_id)
|
|
||||||
movie_list.movies.add(movie)
|
|
||||||
except Movie.DoesNotExist:
|
|
||||||
raise NotFound(f"Movie {movie_id} does not exist")
|
|
||||||
|
|
||||||
removed_movies = Movie.objects.exclude(id__in=movie_ids)
|
|
||||||
for removed_movie in removed_movies:
|
|
||||||
removed_movie.delete()
|
|
||||||
|
|
||||||
movie_list.save()
|
|
||||||
|
|
||||||
return JsonResponse(MovieListSerializer(movie_list).data)
|
|
|
@ -5,4 +5,3 @@ markdown
|
||||||
django-filter
|
django-filter
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
django-cors-headers==4.7.0
|
|
||||||
|
|
6
static/admin/js/vendor/jquery/jquery.js
vendored
6
static/admin/js/vendor/jquery/jquery.js
vendored
|
@ -2144,7 +2144,7 @@ function tokenize( selector, parseOnly ) {
|
||||||
|
|
||||||
// Return the length of the invalid excess
|
// Return the length of the invalid excess
|
||||||
// if we're just parsing
|
// if we're just parsing
|
||||||
// Otherwise, throw an error or return tokens
|
// Otherwise, throw an error or return users
|
||||||
if ( parseOnly ) {
|
if ( parseOnly ) {
|
||||||
return soFar.length;
|
return soFar.length;
|
||||||
}
|
}
|
||||||
|
@ -2152,7 +2152,7 @@ function tokenize( selector, parseOnly ) {
|
||||||
return soFar ?
|
return soFar ?
|
||||||
find.error( selector ) :
|
find.error( selector ) :
|
||||||
|
|
||||||
// Cache the tokens
|
// Cache the users
|
||||||
tokenCache( selector, groups ).slice( 0 );
|
tokenCache( selector, groups ).slice( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2655,7 +2655,7 @@ function select( selector, context, results, seed ) {
|
||||||
testContext( context.parentNode ) || context
|
testContext( context.parentNode ) || context
|
||||||
) ) ) {
|
) ) ) {
|
||||||
|
|
||||||
// If seed is empty or no tokens remain, we can return early
|
// If seed is empty or no users remain, we can return early
|
||||||
tokens.splice( i, 1 );
|
tokens.splice( i, 1 );
|
||||||
selector = seed.length && toSelector( tokens );
|
selector = seed.length && toSelector( tokens );
|
||||||
if ( !selector ) {
|
if ( !selector ) {
|
||||||
|
|
50
static/admin/js/vendor/xregexp/xregexp.js
vendored
50
static/admin/js/vendor/xregexp/xregexp.js
vendored
|
@ -43,7 +43,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
||||||
var _default = function _default(XRegExp) {
|
var _default = function _default(XRegExp) {
|
||||||
/**
|
/**
|
||||||
* Adds base support for Unicode matching:
|
* Adds base support for Unicode matching:
|
||||||
* - Adds syntax `\p{..}` for matching Unicode tokens. Tokens can be inverted using `\P{..}` or
|
* - Adds syntax `\p{..}` for matching Unicode users. Tokens can be inverted using `\P{..}` or
|
||||||
* `\p{^..}`. Token names ignore case, spaces, hyphens, and underscores. You can omit the
|
* `\p{^..}`. Token names ignore case, spaces, hyphens, and underscores. You can omit the
|
||||||
* braces for token names that are a single letter (e.g. `\pL` or `PL`).
|
* braces for token names that are a single letter (e.g. `\pL` or `PL`).
|
||||||
* - Adds flag A (astral), which enables 21-bit Unicode support.
|
* - Adds flag A (astral), which enables 21-bit Unicode support.
|
||||||
|
@ -129,7 +129,7 @@ var _default = function _default(XRegExp) {
|
||||||
var _context2;
|
var _context2;
|
||||||
|
|
||||||
combined += (0, _concat["default"])(_context2 = "".concat(item.astral ? '|' : '', "[")).call(_context2, item.bmp, "]");
|
combined += (0, _concat["default"])(_context2 = "".concat(item.astral ? '|' : '', "[")).call(_context2, item.bmp, "]");
|
||||||
} // Astral Unicode tokens always match a code point, never a code unit
|
} // Astral Unicode users always match a code point, never a code unit
|
||||||
|
|
||||||
|
|
||||||
return isNegated ? "(?:(?!".concat(combined, ")(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\0-\uFFFF]))") : "(?:".concat(combined, ")");
|
return isNegated ? "(?:(?!".concat(combined, ")(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\0-\uFFFF]))") : "(?:".concat(combined, ")");
|
||||||
|
@ -154,7 +154,7 @@ var _default = function _default(XRegExp) {
|
||||||
var ERR_UNKNOWN_NAME = 'Unknown Unicode token ';
|
var ERR_UNKNOWN_NAME = 'Unknown Unicode token ';
|
||||||
var ERR_UNKNOWN_REF = 'Unicode token missing data ';
|
var ERR_UNKNOWN_REF = 'Unicode token missing data ';
|
||||||
var ERR_ASTRAL_ONLY = 'Astral mode required for Unicode token ';
|
var ERR_ASTRAL_ONLY = 'Astral mode required for Unicode token ';
|
||||||
var ERR_ASTRAL_IN_CLASS = 'Astral mode does not support Unicode tokens within character classes';
|
var ERR_ASTRAL_IN_CLASS = 'Astral mode does not support Unicode users within character classes';
|
||||||
|
|
||||||
var _match = (0, _slicedToArray2["default"])(match, 6),
|
var _match = (0, _slicedToArray2["default"])(match, 6),
|
||||||
fullToken = _match[0],
|
fullToken = _match[0],
|
||||||
|
@ -221,7 +221,7 @@ var _default = function _default(XRegExp) {
|
||||||
leadChar: '\\'
|
leadChar: '\\'
|
||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
* Adds to the list of Unicode tokens that XRegExp regexes can match via `\p` or `\P`.
|
* Adds to the list of Unicode users that XRegExp regexes can match via `\p` or `\P`.
|
||||||
*
|
*
|
||||||
* @memberOf XRegExp
|
* @memberOf XRegExp
|
||||||
* @param {Array} data Objects with named character ranges. Each object may have properties
|
* @param {Array} data Objects with named character ranges. Each object may have properties
|
||||||
|
@ -239,7 +239,7 @@ var _default = function _default(XRegExp) {
|
||||||
* points. `inverseOf` can be used to avoid duplicating character data if a Unicode token is
|
* points. `inverseOf` can be used to avoid duplicating character data if a Unicode token is
|
||||||
* defined as the exact inverse of another token.
|
* defined as the exact inverse of another token.
|
||||||
* @param {String} [typePrefix] Enables optionally using this type as a prefix for all of the
|
* @param {String} [typePrefix] Enables optionally using this type as a prefix for all of the
|
||||||
* provided Unicode tokens, e.g. if given `'Type'`, then `\p{TokenName}` can also be written
|
* provided Unicode users, e.g. if given `'Type'`, then `\p{TokenName}` can also be written
|
||||||
* as `\p{Type=TokenName}`.
|
* as `\p{Type=TokenName}`.
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
|
@ -471,7 +471,7 @@ var fixed = {}; // Storage for regexes cached by `XRegExp.cache`
|
||||||
|
|
||||||
var regexCache = {}; // Storage for pattern details cached by the `XRegExp` constructor
|
var regexCache = {}; // Storage for pattern details cached by the `XRegExp` constructor
|
||||||
|
|
||||||
var patternCache = {}; // Storage for regex syntax tokens added internally or by `XRegExp.addToken`
|
var patternCache = {}; // Storage for regex syntax users added internally or by `XRegExp.addToken`
|
||||||
|
|
||||||
var tokens = []; // Token scopes
|
var tokens = []; // Token scopes
|
||||||
|
|
||||||
|
@ -668,7 +668,7 @@ function copyRegex(regex, options) {
|
||||||
xregexpFlags = flagsToAdd ? clipDuplicates((0, _flags["default"])(xData) + flagsToAdd) : (0, _flags["default"])(xData);
|
xregexpFlags = flagsToAdd ? clipDuplicates((0, _flags["default"])(xData) + flagsToAdd) : (0, _flags["default"])(xData);
|
||||||
}
|
}
|
||||||
} // Augment with `XRegExp.prototype` properties, but use the native `RegExp` constructor to avoid
|
} // Augment with `XRegExp.prototype` properties, but use the native `RegExp` constructor to avoid
|
||||||
// searching for special tokens. That would be wrong for regexes constructed by `RegExp`, and
|
// searching for special users. That would be wrong for regexes constructed by `RegExp`, and
|
||||||
// unnecessary for regexes constructed by `XRegExp` because the regex has already undergone the
|
// unnecessary for regexes constructed by `XRegExp` because the regex has already undergone the
|
||||||
// translation to native regex syntax
|
// translation to native regex syntax
|
||||||
|
|
||||||
|
@ -706,16 +706,16 @@ function getContextualTokenSeparator(match, scope, flags) {
|
||||||
var precedingChar = match.input[match.index - 1];
|
var precedingChar = match.input[match.index - 1];
|
||||||
var followingChar = match.input[matchEndPos];
|
var followingChar = match.input[matchEndPos];
|
||||||
|
|
||||||
if ( // No need to separate tokens if at the beginning or end of a group, before or after a
|
if ( // No need to separate users if at the beginning or end of a group, before or after a
|
||||||
// group, or before or after a `|`
|
// group, or before or after a `|`
|
||||||
/^[()|]$/.test(precedingChar) || /^[()|]$/.test(followingChar) || // No need to separate tokens if at the beginning or end of the pattern
|
/^[()|]$/.test(precedingChar) || /^[()|]$/.test(followingChar) || // No need to separate users if at the beginning or end of the pattern
|
||||||
match.index === 0 || matchEndPos === match.input.length || // No need to separate tokens if at the beginning of a noncapturing group or lookaround.
|
match.index === 0 || matchEndPos === match.input.length || // No need to separate users if at the beginning of a noncapturing group or lookaround.
|
||||||
// Looks only at the last 4 chars (at most) for perf when constructing long regexes.
|
// Looks only at the last 4 chars (at most) for perf when constructing long regexes.
|
||||||
/\(\?(?:[:=!]|<[=!])$/.test(match.input.substring(match.index - 4, match.index)) || // Avoid separating tokens when the following token is a quantifier
|
/\(\?(?:[:=!]|<[=!])$/.test(match.input.substring(match.index - 4, match.index)) || // Avoid separating users when the following token is a quantifier
|
||||||
isQuantifierNext(match.input, matchEndPos, flags)) {
|
isQuantifierNext(match.input, matchEndPos, flags)) {
|
||||||
return '';
|
return '';
|
||||||
} // Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`.
|
} // Keep users separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`.
|
||||||
// This also ensures all tokens remain as discrete atoms, e.g. it prevents converting the
|
// This also ensures all users remain as discrete atoms, e.g. it prevents converting the
|
||||||
// syntax error `(? :` into `(?:`.
|
// syntax error `(? :` into `(?:`.
|
||||||
|
|
||||||
|
|
||||||
|
@ -914,13 +914,13 @@ function registerFlag(flag) {
|
||||||
registeredFlags[flag] = true;
|
registeredFlags[flag] = true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Runs built-in and custom regex syntax tokens in reverse insertion order at the specified
|
* Runs built-in and custom regex syntax users in reverse insertion order at the specified
|
||||||
* position, until a match is found.
|
* position, until a match is found.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {String} pattern Original pattern from which an XRegExp object is being built.
|
* @param {String} pattern Original pattern from which an XRegExp object is being built.
|
||||||
* @param {String} flags Flags being used to construct the regex.
|
* @param {String} flags Flags being used to construct the regex.
|
||||||
* @param {Number} pos Position to search for tokens within `pattern`.
|
* @param {Number} pos Position to search for users within `pattern`.
|
||||||
* @param {Number} scope Regex scope to apply: 'default' or 'class'.
|
* @param {Number} scope Regex scope to apply: 'default' or 'class'.
|
||||||
* @param {Object} context Context object to use for token handler functions.
|
* @param {Object} context Context object to use for token handler functions.
|
||||||
* @returns {Object} Object with properties `matchLength`, `output`, and `reparse`; or `null`.
|
* @returns {Object} Object with properties `matchLength`, `output`, and `reparse`; or `null`.
|
||||||
|
@ -1055,14 +1055,14 @@ function XRegExp(pattern, flags) {
|
||||||
|
|
||||||
var applied = prepareFlags(pattern, flags);
|
var applied = prepareFlags(pattern, flags);
|
||||||
var appliedPattern = applied.pattern;
|
var appliedPattern = applied.pattern;
|
||||||
var appliedFlags = (0, _flags["default"])(applied); // Use XRegExp's tokens to translate the pattern to a native regex pattern.
|
var appliedFlags = (0, _flags["default"])(applied); // Use XRegExp's users to translate the pattern to a native regex pattern.
|
||||||
// `appliedPattern.length` may change on each iteration if tokens use `reparse`
|
// `appliedPattern.length` may change on each iteration if users use `reparse`
|
||||||
|
|
||||||
while (pos < appliedPattern.length) {
|
while (pos < appliedPattern.length) {
|
||||||
do {
|
do {
|
||||||
// Check for custom tokens at the current position
|
// Check for custom users at the current position
|
||||||
result = runTokens(appliedPattern, appliedFlags, pos, scope, context); // If the matched token used the `reparse` option, splice its output into the
|
result = runTokens(appliedPattern, appliedFlags, pos, scope, context); // If the matched token used the `reparse` option, splice its output into the
|
||||||
// pattern before running tokens again at the same position
|
// pattern before running users again at the same position
|
||||||
|
|
||||||
if (result && result.reparse) {
|
if (result && result.reparse) {
|
||||||
appliedPattern = (0, _slice["default"])(appliedPattern).call(appliedPattern, 0, pos) + result.output + (0, _slice["default"])(appliedPattern).call(appliedPattern, pos + result.matchLength);
|
appliedPattern = (0, _slice["default"])(appliedPattern).call(appliedPattern, 0, pos) + result.output + (0, _slice["default"])(appliedPattern).call(appliedPattern, pos + result.matchLength);
|
||||||
|
@ -1091,7 +1091,7 @@ function XRegExp(pattern, flags) {
|
||||||
|
|
||||||
patternCache[pattern][flags] = {
|
patternCache[pattern][flags] = {
|
||||||
// Use basic cleanup to collapse repeated empty groups like `(?:)(?:)` to `(?:)`. Empty
|
// Use basic cleanup to collapse repeated empty groups like `(?:)(?:)` to `(?:)`. Empty
|
||||||
// groups are sometimes inserted during regex transpilation in order to keep tokens
|
// groups are sometimes inserted during regex transpilation in order to keep users
|
||||||
// separated. However, more than one empty group in a row is never needed.
|
// separated. However, more than one empty group in a row is never needed.
|
||||||
pattern: output.replace(/(?:\(\?:\))+/g, '(?:)'),
|
pattern: output.replace(/(?:\(\?:\))+/g, '(?:)'),
|
||||||
// Strip all but native flags
|
// Strip all but native flags
|
||||||
|
@ -1151,7 +1151,7 @@ XRegExp._pad4 = pad4;
|
||||||
* not required to trigger the token. This registers the flags, to prevent XRegExp from
|
* not required to trigger the token. This registers the flags, to prevent XRegExp from
|
||||||
* throwing an 'unknown flag' error when any of the flags are used.
|
* throwing an 'unknown flag' error when any of the flags are used.
|
||||||
* - `reparse` {Boolean} Whether the `handler` function's output should not be treated as
|
* - `reparse` {Boolean} Whether the `handler` function's output should not be treated as
|
||||||
* final, and instead be reparseable by other tokens (including the current token). Allows
|
* final, and instead be reparseable by other users (including the current token). Allows
|
||||||
* token chaining or deferring.
|
* token chaining or deferring.
|
||||||
* - `leadChar` {String} Single character that occurs at the beginning of any successful match
|
* - `leadChar` {String} Single character that occurs at the beginning of any successful match
|
||||||
* of the token (not always applicable). This doesn't change the behavior of the token unless
|
* of the token (not always applicable). This doesn't change the behavior of the token unless
|
||||||
|
@ -1204,7 +1204,7 @@ XRegExp.addToken = function (regex, handler, options) {
|
||||||
} finally {
|
} finally {
|
||||||
_iterator2.f();
|
_iterator2.f();
|
||||||
}
|
}
|
||||||
} // Add to the private list of syntax tokens
|
} // Add to the private list of syntax users
|
||||||
|
|
||||||
|
|
||||||
tokens.push({
|
tokens.push({
|
||||||
|
@ -2035,7 +2035,7 @@ fixed.match = function (regex) {
|
||||||
return fixed.exec.call(regex, nullThrows(this));
|
return fixed.exec.call(regex, nullThrows(this));
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Adds support for `${n}` (or `$<n>`) tokens for named and numbered backreferences in replacement
|
* Adds support for `${n}` (or `$<n>`) users for named and numbered backreferences in replacement
|
||||||
* text, and provides named backreferences to replacement functions as `arguments[0].name`. Also
|
* text, and provides named backreferences to replacement functions as `arguments[0].name`. Also
|
||||||
* fixes browser bugs in replacement text syntax when performing a replacement using a nonregex
|
* fixes browser bugs in replacement text syntax when performing a replacement using a nonregex
|
||||||
* search value, and the value of a replacement regex's `lastIndex` property during replacement
|
* search value, and the value of a replacement regex's `lastIndex` property during replacement
|
||||||
|
@ -2271,7 +2271,7 @@ fixed.split = function (separator, limit) {
|
||||||
separator.lastIndex = origLastIndex;
|
separator.lastIndex = origLastIndex;
|
||||||
return output.length > limit ? (0, _slice["default"])(output).call(output, 0, limit) : output;
|
return output.length > limit ? (0, _slice["default"])(output).call(output, 0, limit) : output;
|
||||||
}; // ==--------------------------==
|
}; // ==--------------------------==
|
||||||
// Built-in syntax/flag tokens
|
// Built-in syntax/flag users
|
||||||
// ==--------------------------==
|
// ==--------------------------==
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2310,7 +2310,7 @@ XRegExp.addToken(/\\u{([\dA-Fa-f]+)}/, function (match, scope, flags) {
|
||||||
|
|
||||||
if (code <= 0xFFFF) {
|
if (code <= 0xFFFF) {
|
||||||
// Converting to \uNNNN avoids needing to escape the literal character and keep it
|
// Converting to \uNNNN avoids needing to escape the literal character and keep it
|
||||||
// separate from preceding tokens
|
// separate from preceding users
|
||||||
return "\\u".concat(pad4(hex(code)));
|
return "\\u".concat(pad4(hex(code)));
|
||||||
} // If `code` is between 0xFFFF and 0x10FFFF, require and defer to native handling
|
} // If `code` is between 0xFFFF and 0x10FFFF, require and defer to native handling
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from django.contrib.auth.models import Group, User, AnonymousUser
|
||||||
from rest_framework import permissions, viewsets, status
|
from rest_framework import permissions, viewsets, status
|
||||||
from knox.auth import TokenAuthentication
|
from knox.auth import TokenAuthentication
|
||||||
from knox.views import LoginView as KnoxLoginView
|
from knox.views import LoginView as KnoxLoginView
|
||||||
|
from rest_framework.authentication import BasicAuthentication
|
||||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
|
|
Loading…
Add table
Reference in a new issue