Compare commits

...

10 commits

27 changed files with 280 additions and 53 deletions

View file

@ -6,5 +6,4 @@ 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
View file

@ -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="Python 3.13 (movie-night-py)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.12.7 Docker (&lt;none&gt;:&lt;none&gt;)" project-jdk-type="Python SDK" />
</project> </project>

View file

@ -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="Python 3.13 (movie-night-py)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Remote Python 3.12.7 Docker (&lt;none&gt;:&lt;none&gt;)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

View file

@ -35,4 +35,4 @@ USER web
EXPOSE 8000 EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "djangodocker.wsgi:application"] CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "movienight.wsgi:application"]

5
README.md Normal file
View file

@ -0,0 +1,5 @@
Movie Night API
=
Requirements:
- Docker

View file

@ -1,6 +1,7 @@
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}
@ -10,16 +11,23 @@ services:
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
- djangodocker_data:/var/lib/postgresql/data - movienight_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: djangodocker-api container_name: movienight-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}
@ -40,4 +48,4 @@ services:
- /etc/group:/etc/group:ro - /etc/group:/etc/group:ro
volumes: volumes:
djangodocker_data: movienight_data:

View file

@ -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="djangodocker" PROJECT_NAME="movienight"
fi fi
echo "===== UPDATING PROJECT NAME =====" echo "===== UPDATING PROJECT NAME ====="
git ls-files | xargs sed -i "s/djangodocker/${PROJECT_NAME}/g" git ls-files | xargs sed -i "s/movienight/${PROJECT_NAME}/g"
echo "Done!" echo "Done!"
echo "===== UPDATING ENVIRONMENT =====" echo "===== UPDATING ENVIRONMENT ====="
cp .env.example .env cp .env.example .env
sed -i "s/djangodocker/${PROJECT_NAME}/g" ./.env sed -i "s/movienight/${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,8 +30,10 @@ 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}"
@ -39,7 +41,10 @@ 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
mv djangodocker "$PROJECT_NAME" # RENAME PROJECT DIRECTORY
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
@ -47,7 +52,15 @@ 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 "===== CREATE SUPERUSER =====" echo "===== CREATING 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

View file

@ -6,7 +6,7 @@ import sys
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangodocker.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.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:

3
movie_manager/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
movie_manager/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MovieManagerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'movie_manager'

View file

@ -0,0 +1,48 @@
# 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)),
],
),
]

View file

@ -0,0 +1,26 @@
# 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'),
),
]

View file

40
movie_manager/models.py Normal file
View file

@ -0,0 +1,40 @@
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

View file

@ -0,0 +1,21 @@
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())

3
movie_manager/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

48
movie_manager/views.py Normal file
View file

@ -0,0 +1,48 @@
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)

0
movienight/__init__.py Normal file
View file

View file

@ -1,5 +1,5 @@
""" """
ASGI config for djangodocker project. ASGI config for movienight 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', 'djangodocker.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.settings')
application = get_asgi_application() application = get_asgi_application()

View file

@ -28,6 +28,7 @@ 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
@ -42,11 +43,14 @@ 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",
@ -54,7 +58,7 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
ROOT_URLCONF = "djangodocker.urls" ROOT_URLCONF = "movienight.urls"
TEMPLATES = [ TEMPLATES = [
{ {
@ -72,7 +76,7 @@ TEMPLATES = [
}, },
] ]
WSGI_APPLICATION = "djangodocker.wsgi.application" WSGI_APPLICATION = "movienight.wsgi.application"
# Database # Database

View file

@ -1,5 +1,5 @@
""" """
URL configuration for djangodocker project. URL configuration for movienight project.
""" """
import knox import knox
@ -11,11 +11,14 @@ 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)),

View file

@ -1,5 +1,5 @@
""" """
WSGI config for djangodocker project. WSGI config for movienight 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', 'djangodocker.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movienight.settings')
application = get_wsgi_application() application = get_wsgi_application()

View file

@ -5,3 +5,4 @@ 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

View file

@ -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 users // Otherwise, throw an error or return tokens
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 users // Cache the tokens
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 users remain, we can return early // If seed is empty or no tokens 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 ) {

View file

@ -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 users. Tokens can be inverted using `\P{..}` or * - Adds syntax `\p{..}` for matching Unicode tokens. 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 users always match a code point, never a code unit } // Astral Unicode tokens 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 users within character classes'; var ERR_ASTRAL_IN_CLASS = 'Astral mode does not support Unicode tokens 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 users that XRegExp regexes can match via `\p` or `\P`. * Adds to the list of Unicode tokens 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 users, e.g. if given `'Type'`, then `\p{TokenName}` can also be written * provided Unicode tokens, 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 users added internally or by `XRegExp.addToken` var patternCache = {}; // Storage for regex syntax tokens 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 users. That would be wrong for regexes constructed by `RegExp`, and // searching for special tokens. 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 users if at the beginning or end of a group, before or after a if ( // No need to separate tokens 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 users if at the beginning or end of the pattern /^[()|]$/.test(precedingChar) || /^[()|]$/.test(followingChar) || // No need to separate tokens if at the beginning or end of the pattern
match.index === 0 || matchEndPos === match.input.length || // No need to separate users if at the beginning of a noncapturing group or lookaround. match.index === 0 || matchEndPos === match.input.length || // No need to separate tokens 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 users when the following token is a quantifier /\(\?(?:[:=!]|<[=!])$/.test(match.input.substring(match.index - 4, match.index)) || // Avoid separating tokens when the following token is a quantifier
isQuantifierNext(match.input, matchEndPos, flags)) { isQuantifierNext(match.input, matchEndPos, flags)) {
return ''; return '';
} // Keep users separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`. } // Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`.
// This also ensures all users remain as discrete atoms, e.g. it prevents converting the // This also ensures all tokens 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 users in reverse insertion order at the specified * Runs built-in and custom regex syntax tokens 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 users within `pattern`. * @param {Number} pos Position to search for tokens 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 users to translate the pattern to a native regex pattern. var appliedFlags = (0, _flags["default"])(applied); // Use XRegExp's tokens to translate the pattern to a native regex pattern.
// `appliedPattern.length` may change on each iteration if users use `reparse` // `appliedPattern.length` may change on each iteration if tokens use `reparse`
while (pos < appliedPattern.length) { while (pos < appliedPattern.length) {
do { do {
// Check for custom users at the current position // Check for custom tokens 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 users again at the same position // pattern before running tokens 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 users // groups are sometimes inserted during regex transpilation in order to keep tokens
// 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 users (including the current token). Allows * final, and instead be reparseable by other tokens (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 users } // Add to the private list of syntax tokens
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>`) users for named and numbered backreferences in replacement * Adds support for `${n}` (or `$<n>`) tokens 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 users // Built-in syntax/flag tokens
// ==--------------------------== // ==--------------------------==
/* /*
@ -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 users // separate from preceding tokens
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

View file

@ -3,7 +3,6 @@ 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