├── .gitignore ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── db.sqlite3 ├── manage.py └── movierater ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | static 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | server/src/nectar/local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # IPython Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | venv/ 87 | ENV/ 88 | venv2 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | *.idea 97 | !/server/src/nectar/settings.py 98 | *.swp 99 | *.db 100 | .DS_Store 101 | pylintrc 102 | *.pyc 103 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nogostradamus/course-django-react-api/5c28898fae1d039edc38f3d425916e7504004982/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Movie, Rating 3 | 4 | admin.site.register(Movie) 5 | admin.site.register(Rating) 6 | -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-16 22:14 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Movie', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('title', models.CharField(max_length=32)), 23 | ('description', models.TextField(max_length=360)), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='Rating', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('stars', models.IntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])), 31 | ('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Movie')), 32 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 33 | ], 34 | options={ 35 | 'unique_together': {('user', 'movie')}, 36 | 'index_together': {('user', 'movie')}, 37 | }, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nogostradamus/course-django-react-api/5c28898fae1d039edc38f3d425916e7504004982/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.core.validators import MaxValueValidator, MinValueValidator 4 | 5 | class Movie(models.Model): 6 | title = models.CharField(max_length=32) 7 | description = models.TextField(max_length=360) 8 | 9 | def no_of_ratings(self): 10 | ratings = Rating.objects.filter(movie=self) 11 | return len(ratings) 12 | 13 | def avg_rating(self): 14 | sum = 0 15 | ratings = Rating.objects.filter(movie=self) 16 | for rating in ratings: 17 | sum += rating.stars 18 | 19 | if len(ratings) > 0: 20 | return sum / len(ratings) 21 | else: 22 | return 0 23 | 24 | class Rating(models.Model): 25 | movie = models.ForeignKey(Movie, on_delete=models.CASCADE) 26 | user = models.ForeignKey(User, on_delete=models.CASCADE) 27 | stars = models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(5)]) 28 | 29 | class Meta: 30 | unique_together = (('user', 'movie'),) 31 | index_together = (('user', 'movie'),) 32 | -------------------------------------------------------------------------------- /api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Movie, Rating 3 | from django.contrib.auth.models import User 4 | from rest_framework.authtoken.models import Token 5 | 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ('id', 'username', 'password') 10 | extra_kwargs = {'password': {'write_only': True, 'required': True}} 11 | 12 | def create(self, validated_data): 13 | user = User.objects.create_user(**validated_data) 14 | Token.objects.create(user=user) 15 | return user 16 | 17 | class MovieSerializer(serializers.ModelSerializer): 18 | class Meta: 19 | model = Movie 20 | fields = ('id', 'title', 'description', 'no_of_ratings', 'avg_rating') 21 | 22 | class RatingSerializer(serializers.ModelSerializer): 23 | class Meta: 24 | model = Rating 25 | fields = ('id', 'stars', 'user', 'movie') -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework import routers 3 | from django.conf.urls import include 4 | from .views import MovieViewSet, RatingViewSet, UserViewSet 5 | 6 | router = routers.DefaultRouter() 7 | router.register('users', UserViewSet) 8 | router.register('movies', MovieViewSet) 9 | router.register('ratings', RatingViewSet) 10 | 11 | urlpatterns = [ 12 | path('', include(router.urls)), 13 | ] 14 | -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets, status 2 | from rest_framework.response import Response 3 | from rest_framework.decorators import action 4 | from rest_framework.authentication import TokenAuthentication 5 | from rest_framework.permissions import IsAuthenticated, AllowAny 6 | from django.contrib.auth.models import User 7 | from .models import Movie, Rating 8 | from .serializers import MovieSerializer, RatingSerializer, UserSerializer 9 | 10 | class UserViewSet(viewsets.ModelViewSet): 11 | queryset = User.objects.all() 12 | serializer_class = UserSerializer 13 | permission_classes = (AllowAny,) 14 | 15 | class MovieViewSet(viewsets.ModelViewSet): 16 | queryset = Movie.objects.all() 17 | serializer_class = MovieSerializer 18 | authentication_classes = (TokenAuthentication, ) 19 | permission_classes = (IsAuthenticated,) 20 | 21 | @action(detail=True, methods=['POST']) 22 | def rate_movie(self, request, pk=None): 23 | if 'stars' in request.data: 24 | 25 | movie = Movie.objects.get(id=pk) 26 | stars = request.data['stars'] 27 | user = request.user 28 | 29 | try: 30 | rating = Rating.objects.get(user=user.id, movie=movie.id) 31 | rating.stars = stars 32 | rating.save() 33 | serializer = RatingSerializer(rating, many=False) 34 | response = {'message': 'Rating updated', 'result': serializer.data} 35 | return Response(response, status=status.HTTP_200_OK) 36 | except: 37 | rating = Rating.objects.create(user=user, movie=movie, stars=stars) 38 | serializer = RatingSerializer(rating, many=False) 39 | response = {'message': 'Rating created', 'result': serializer.data} 40 | return Response(response, status=status.HTTP_200_OK) 41 | 42 | else: 43 | response = {'message': 'You need to provide stars'} 44 | return Response(response, status=status.HTTP_400_BAD_REQUEST) 45 | 46 | class RatingViewSet(viewsets.ModelViewSet): 47 | queryset = Rating.objects.all() 48 | serializer_class = RatingSerializer 49 | authentication_classes = (TokenAuthentication, ) 50 | permission_classes = (IsAuthenticated,) 51 | 52 | def update(self, request, *args, **kwargs): 53 | response = {'message': 'You cant update rating like that'} 54 | return Response(response, status=status.HTTP_400_BAD_REQUEST) 55 | 56 | def create(self, request, *args, **kwargs): 57 | response = {'message': 'You cant create rating like that'} 58 | return Response(response, status=status.HTTP_400_BAD_REQUEST) 59 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nogostradamus/course-django-react-api/5c28898fae1d039edc38f3d425916e7504004982/db.sqlite3 -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movierater.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /movierater/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nogostradamus/course-django-react-api/5c28898fae1d039edc38f3d425916e7504004982/movierater/__init__.py -------------------------------------------------------------------------------- /movierater/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for movierater project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'chddi0zp$jf8_p6l*n1_cr)fhvu3+k3#=-x$^&u*gos92oiz*#' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'rest_framework.authtoken', 42 | 'corsheaders', 43 | 'api' 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'corsheaders.middleware.CorsMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | CORS_ORIGIN_WHITELIST = ( 58 | 'http://localhost:3000', 'htpp://localhost:4200' 59 | ) 60 | 61 | ROOT_URLCONF = 'movierater.urls' 62 | 63 | TEMPLATES = [ 64 | { 65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 66 | 'DIRS': [], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'movierater.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 84 | 85 | DATABASES = { 86 | 'default': { 87 | 'ENGINE': 'django.db.backends.sqlite3', 88 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 89 | } 90 | } 91 | 92 | REST_FRAMEWORK = { 93 | 'DEFAULT_PERMISSION_CLASSES': [ 94 | 'rest_framework.permissions.IsAuthenticated', 95 | ] 96 | } 97 | 98 | 99 | # Password validation 100 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 101 | 102 | AUTH_PASSWORD_VALIDATORS = [ 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 114 | }, 115 | ] 116 | 117 | 118 | # Internationalization 119 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 120 | 121 | LANGUAGE_CODE = 'en-us' 122 | 123 | TIME_ZONE = 'UTC' 124 | 125 | USE_I18N = True 126 | 127 | USE_L10N = True 128 | 129 | USE_TZ = True 130 | 131 | 132 | # Static files (CSS, JavaScript, Images) 133 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 134 | 135 | STATIC_URL = '/static/' 136 | -------------------------------------------------------------------------------- /movierater/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path 3 | from django.conf.urls import include 4 | from rest_framework.authtoken.views import obtain_auth_token 5 | 6 | urlpatterns = [ 7 | path('admin/', admin.site.urls), 8 | path('api/', include('api.urls')), 9 | path('auth/', obtain_auth_token), 10 | ] 11 | -------------------------------------------------------------------------------- /movierater/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for movierater project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'movierater.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------