├── apps ├── __init__.py ├── cards │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0005_auto_20201201_2143.py │ │ ├── 0002_auto_20201119_1552.py │ │ ├── 0003_auto_20201119_1604.py │ │ ├── 0004_auto_20201126_1400.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── views.py │ └── models.py ├── decks │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_auto_20201119_1552.py │ │ ├── 0003_auto_20201119_1557.py │ │ ├── 0001_initial.py │ │ └── 0004_auto_20201126_1400.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── models.py │ ├── urls.py │ └── views.py ├── users │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── views.py │ ├── apps.py │ ├── admin.py │ └── models.py └── utils │ ├── __init__.py │ ├── migrations │ └── __init__.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ ├── apps.py │ └── models.py ├── config ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── poetry.toml ├── README.md ├── pyproject.toml ├── manage.py ├── .gitignore └── poetry.lock /apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/cards/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/decks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/cards/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/decks/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/utils/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | create = false 3 | -------------------------------------------------------------------------------- /apps/cards/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/decks/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/utils/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/users/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /apps/utils/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/utils/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /apps/cards/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CardsConfig(AppConfig): 5 | name = 'cards' 6 | -------------------------------------------------------------------------------- /apps/decks/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DecksConfig(AppConfig): 5 | name = 'decks' 6 | -------------------------------------------------------------------------------- /apps/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /apps/utils/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UtilsConfig(AppConfig): 5 | name = 'utils' 6 | -------------------------------------------------------------------------------- /apps/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import User 3 | 4 | 5 | class UserAdmin(admin.ModelAdmin): 6 | list_display = ('email', 'is_admin') 7 | 8 | 9 | admin.site.register(User, UserAdmin) 10 | -------------------------------------------------------------------------------- /apps/cards/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Card 3 | 4 | 5 | class CardAdmin(admin.ModelAdmin): 6 | list_display = ('deck', 'question', 'bucket') 7 | 8 | 9 | admin.site.register(Card, CardAdmin) 10 | -------------------------------------------------------------------------------- /apps/decks/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Deck 3 | 4 | 5 | class DeckAdmin(admin.ModelAdmin): 6 | list_display = ('title', 'description', 'last_reviewed') 7 | 8 | 9 | admin.site.register(Deck, DeckAdmin) 10 | -------------------------------------------------------------------------------- /apps/utils/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Timestamps(models.Model): 5 | created_at = models.DateTimeField(auto_now_add=True) 6 | updated_at = models.DateTimeField(auto_now=True) 7 | 8 | class Meta: 9 | abstract = True 10 | -------------------------------------------------------------------------------- /apps/cards/urls.py: -------------------------------------------------------------------------------- 1 | # /cards /cards/:id 2 | from django.urls import path, include 3 | from rest_framework import routers 4 | from .views import CardsViewSet 5 | 6 | router = routers.DefaultRouter() 7 | router.register(r'', CardsViewSet) 8 | 9 | urlpatterns = [ 10 | path('', include(router.urls)), 11 | ] 12 | -------------------------------------------------------------------------------- /apps/decks/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from apps.utils.models import Timestamps 3 | 4 | 5 | class Deck(Timestamps): 6 | title = models.CharField(max_length=100) 7 | description = models.TextField() 8 | last_reviewed = models.DateTimeField(blank=True, null=True) 9 | 10 | def __str__(self): 11 | return self.title 12 | 13 | # TODO: def total_cards(): 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spaced-repetition-django 2 | 3 | Django with GraphQL 4 | Spaced repetition learning back-end with MongoDB. You can see the GraphQL Postgres version of this [here](https://github.com/gwenf/django-graphql-srl) and a DRF Mongo version [here](https://github.com/faraday-academy/django-mongo-srl). 5 | 6 | *Wiki, steps for setting up this project: https://github.com/faraday-academy/django-setup-wiki* 7 | -------------------------------------------------------------------------------- /apps/decks/migrations/0002_auto_20201119_1552.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 15:52 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('decks', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='deck', 15 | old_name='lastReviewed', 16 | new_name='last_reviewed', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for SpacedRepetition project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SpacedRepetition.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for SpacedRepetition 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/3.1/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', 'SpacedRepetition.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /apps/decks/migrations/0003_auto_20201119_1557.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 15:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('decks', '0002_auto_20201119_1552'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='deck', 15 | name='last_reviewed', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "spacedrepetition" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["gwenf "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.6" 10 | djangorestframework = "^3.12.2" 11 | psycopg2 = "^2.8.6" 12 | drf-nested-routers = "^0.92.5" 13 | django-cors-headers = "^3.5.0" 14 | 15 | [tool.poetry.dev-dependencies] 16 | flake8 = "^3.8.4" 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /apps/cards/migrations/0005_auto_20201201_2143.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-12-01 21:43 2 | 3 | import datetime 4 | from django.db import migrations, models 5 | from django.utils.timezone import utc 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cards', '0004_auto_20201126_1400'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='card', 17 | name='next_review_at', 18 | field=models.DateTimeField(default=datetime.datetime(2020, 12, 1, 21, 43, 46, 805599, tzinfo=utc)), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/cards/migrations/0002_auto_20201119_1552.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 15:52 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('cards', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='card', 15 | old_name='lastReviewedAt', 16 | new_name='last_reviewed_at', 17 | ), 18 | migrations.RenameField( 19 | model_name='card', 20 | old_name='nextReviewAt', 21 | new_name='next_review_at', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /apps/cards/migrations/0003_auto_20201119_1604.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 16:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('cards', '0002_auto_20201119_1552'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='card', 15 | name='last_reviewed_at', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='card', 20 | name='next_review_at', 21 | field=models.DateTimeField(auto_now_add=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /apps/decks/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 15:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Deck', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=100)), 19 | ('description', models.TextField()), 20 | ('lastReviewed', models.DateTimeField()), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /apps/cards/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from rest_framework import viewsets, serializers 3 | from apps.decks.models import Deck 4 | from .models import Card 5 | 6 | 7 | class CardsSerializer(serializers.ModelSerializer): 8 | deck = serializers.PrimaryKeyRelatedField( 9 | queryset=Deck.objects.all()) 10 | bucket = serializers.IntegerField(read_only=True) 11 | 12 | class Meta: 13 | model = Card 14 | fields = ('id', 'deck', 'question', 'answer', 15 | 'bucket', 'created_at', 'updated_at', 16 | 'next_review_at') 17 | 18 | 19 | class CardsViewSet(viewsets.ModelViewSet): 20 | queryset = Card.objects.all() 21 | serializer_class = CardsSerializer 22 | -------------------------------------------------------------------------------- /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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /apps/cards/migrations/0004_auto_20201126_1400.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-26 14:00 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('cards', '0003_auto_20201119_1604'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='card', 16 | name='created_at', 17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='card', 22 | name='updated_at', 23 | field=models.DateTimeField(auto_now=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /apps/cards/models.py: -------------------------------------------------------------------------------- 1 | from django.utils import timezone 2 | 3 | from django.db import models 4 | from apps.decks.models import Deck 5 | from apps.utils.models import Timestamps 6 | 7 | 8 | class Card(Timestamps): 9 | deck = models.ForeignKey(Deck, on_delete=models.CASCADE) 10 | question = models.TextField() 11 | answer = models.TextField() 12 | buckets = ( 13 | (1, '1 Day'), 14 | (2, '3 Days'), 15 | (3, '7 Days'), 16 | (4, '16 Days'), 17 | (5, '30 Days'), 18 | ) 19 | bucket = models.IntegerField(choices=buckets, default=1) 20 | next_review_at = models.DateTimeField(default=timezone.now()) 21 | last_reviewed_at = models.DateTimeField(blank=True, null=True) 22 | 23 | def __str__(self): 24 | return self.question 25 | -------------------------------------------------------------------------------- /apps/decks/migrations/0004_auto_20201126_1400.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-26 14:00 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('decks', '0003_auto_20201119_1557'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='deck', 16 | name='created_at', 17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='deck', 22 | name='updated_at', 23 | field=models.DateTimeField(auto_now=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /apps/decks/urls.py: -------------------------------------------------------------------------------- 1 | # /decks /decks/:id 2 | # /decks/:id/cards 3 | from django.urls import path, include 4 | from rest_framework_nested import routers 5 | from .views import DecksViewSet, CardsViewSet, TodaysCardsViewSet 6 | 7 | router = routers.SimpleRouter() 8 | router.register(r'', DecksViewSet) 9 | 10 | cards_router = routers.NestedSimpleRouter(router, r'', lookup='decks') 11 | cards_router.register(r'cards', CardsViewSet, basename='deck_cards') 12 | 13 | todays_cards_router = routers.NestedSimpleRouter(router, r'', lookup='decks') 14 | todays_cards_router.register(r'todays-cards', TodaysCardsViewSet, basename='todays_cards') 15 | 16 | urlpatterns = [ 17 | path('', include(router.urls)), 18 | path('', include(cards_router.urls)), 19 | path('', include(todays_cards_router.urls)), 20 | ] 21 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | """SpacedRepetition URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('rest_framework.urls')), 22 | path('decks/', include('apps.decks.urls')), 23 | path('cards/', include('apps.cards.urls')), 24 | ] 25 | -------------------------------------------------------------------------------- /apps/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 14:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='User', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('password', models.CharField(max_length=128, verbose_name='password')), 19 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 20 | ('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')), 21 | ('is_active', models.BooleanField(default=True)), 22 | ('is_admin', models.BooleanField(default=False)), 23 | ], 24 | options={ 25 | 'abstract': False, 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /apps/cards/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-19 15:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('decks', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Card', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('question', models.TextField()), 21 | ('answer', models.TextField()), 22 | ('bucket', models.IntegerField(choices=[(1, '1 Day'), (2, '3 Days'), (3, '7 Days'), (4, '16 Days'), (5, '30 Days')], default=1)), 23 | ('nextReviewAt', models.DateTimeField()), 24 | ('lastReviewedAt', models.DateTimeField()), 25 | ('deck', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='decks.deck')), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /apps/decks/views.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from django.shortcuts import render 4 | from rest_framework import viewsets, serializers 5 | from rest_framework.response import Response 6 | 7 | from .models import Deck 8 | from apps.cards.models import Card 9 | from apps.cards.views import CardsSerializer 10 | 11 | 12 | class DecksSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = Deck 15 | fields = ('id', 'title', 'description', 16 | 'created_at', 'updated_at') 17 | 18 | 19 | class DecksViewSet(viewsets.ModelViewSet): 20 | queryset = Deck.objects.all() 21 | serializer_class = DecksSerializer 22 | 23 | 24 | class CardsViewSet(viewsets.ViewSet): 25 | def list(self, request, decks_pk): 26 | queryset = Card.objects.filter(deck=decks_pk) 27 | serializer = CardsSerializer(queryset, many=True) 28 | return Response(serializer.data) 29 | 30 | 31 | class TodaysCardsViewSet(viewsets.ViewSet): 32 | def list(self, request, decks_pk): 33 | """ 34 | Users can get the list of cards from a deck that they 35 | need to study today. 36 | This should include cards from previous dates that were 37 | not studied yet. 38 | """ 39 | today = date.today() 40 | queryset = Card.objects.filter(deck=decks_pk, 41 | next_review_at__lt=today) 42 | serializer = CardsSerializer(queryset, many=True) 43 | return Response(serializer.data) 44 | -------------------------------------------------------------------------------- /apps/users/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import ( 3 | BaseUserManager, AbstractBaseUser 4 | ) 5 | 6 | 7 | class UserManager(BaseUserManager): 8 | def create_user(self, email, password=None): 9 | """ 10 | Creates and saves a User with the given email and password. 11 | """ 12 | if not email: 13 | raise ValueError('Users must have an email address') 14 | 15 | user = self.model( 16 | email=self.normalize_email(email), 17 | ) 18 | 19 | user.set_password(password) 20 | user.save(using=self._db) 21 | return user 22 | 23 | def create_superuser(self, email, password=None): 24 | """ 25 | Creates and saves a superuser with the given email and password. 26 | """ 27 | user = self.create_user( 28 | email, 29 | password=password, 30 | ) 31 | user.is_admin = True 32 | user.save(using=self._db) 33 | return user 34 | 35 | 36 | class User(AbstractBaseUser): 37 | email = models.EmailField( 38 | verbose_name='email address', 39 | max_length=255, 40 | unique=True, 41 | ) 42 | is_active = models.BooleanField(default=True) 43 | is_admin = models.BooleanField(default=False) 44 | 45 | objects = UserManager() 46 | 47 | USERNAME_FIELD = 'email' 48 | 49 | def __str__(self): 50 | return self.email 51 | 52 | def has_perm(self, perm, obj=None): 53 | "Does the user have a specific permission?" 54 | # Simplest possible answer: Yes, always 55 | return True 56 | 57 | def has_module_perms(self, app_label): 58 | "Does the user have permissions to view the app `app_label`?" 59 | # Simplest possible answer: Yes, always 60 | return True 61 | 62 | @property 63 | def is_staff(self): 64 | "Is the user a member of staff?" 65 | # Simplest possible answer: All admins are staff 66 | return self.is_admin 67 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for Spaced-Repetition project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '&tr)=cadu1nxjneyxq!3&xvijjy0v5$3)je=p9c^4xmwk)449=' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | # CORS_ALLOW_ALL_ORIGINS = True 31 | CORS_ALLOWED_ORIGINS = [ 32 | "http://localhost:8080", 33 | ] 34 | 35 | AUTH_USER_MODEL = 'users.User' 36 | 37 | 38 | # Application definition 39 | 40 | INSTALLED_APPS = [ 41 | 'django.contrib.admin', 42 | 'django.contrib.auth', 43 | 'django.contrib.contenttypes', 44 | 'django.contrib.sessions', 45 | 'django.contrib.messages', 46 | 'django.contrib.staticfiles', 47 | 'corsheaders', 48 | 'rest_framework', 49 | 'rest_framework_nested', 50 | 'apps.utils', 51 | 'apps.users', 52 | 'apps.decks', 53 | 'apps.cards', 54 | ] 55 | 56 | MIDDLEWARE = [ 57 | 'corsheaders.middleware.CorsMiddleware', 58 | 'django.middleware.security.SecurityMiddleware', 59 | 'django.contrib.sessions.middleware.SessionMiddleware', 60 | 'django.middleware.common.CommonMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.messages.middleware.MessageMiddleware', 64 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 65 | ] 66 | 67 | ROOT_URLCONF = 'config.urls' 68 | 69 | TEMPLATES = [ 70 | { 71 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 72 | 'DIRS': [], 73 | 'APP_DIRS': True, 74 | 'OPTIONS': { 75 | 'context_processors': [ 76 | 'django.template.context_processors.debug', 77 | 'django.template.context_processors.request', 78 | 'django.contrib.auth.context_processors.auth', 79 | 'django.contrib.messages.context_processors.messages', 80 | ], 81 | }, 82 | }, 83 | ] 84 | 85 | WSGI_APPLICATION = 'config.wsgi.application' 86 | 87 | 88 | 89 | # Database 90 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 91 | 92 | DATABASES = { 93 | 'default': { 94 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 95 | 'NAME': 'spaced_repetition', 96 | 'USER': 'sr_admin', 97 | 'PASSWORD': '', 98 | 'HOST': 'localhost', 99 | 'PORT': '', 100 | } 101 | } 102 | 103 | 104 | # Password validation 105 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 106 | 107 | AUTH_PASSWORD_VALIDATORS = [ 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 116 | }, 117 | { 118 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 119 | }, 120 | ] 121 | 122 | 123 | # Internationalization 124 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 125 | 126 | LANGUAGE_CODE = 'en-us' 127 | 128 | TIME_ZONE = 'UTC' 129 | 130 | USE_I18N = True 131 | 132 | USE_L10N = True 133 | 134 | USE_TZ = True 135 | 136 | 137 | # Static files (CSS, JavaScript, Images) 138 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 139 | 140 | STATIC_URL = '/static/' 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/vim,macos,windows,linux,vscode,python,django 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=vim,macos,windows,linux,vscode,python,django 3 | 4 | ### Django ### 5 | *.log 6 | *.pot 7 | *.pyc 8 | __pycache__/ 9 | local_settings.py 10 | db.sqlite3 11 | db.sqlite3-journal 12 | media 13 | 14 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 15 | # in your Git repository. Update and uncomment the following line accordingly. 16 | # /staticfiles/ 17 | 18 | ### Django.Python Stack ### 19 | # Byte-compiled / optimized / DLL files 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | *.py,cover 68 | .hypothesis/ 69 | .pytest_cache/ 70 | pytestdebug.log 71 | 72 | # Translations 73 | *.mo 74 | 75 | # Django stuff: 76 | 77 | # Flask stuff: 78 | instance/ 79 | .webassets-cache 80 | 81 | # Scrapy stuff: 82 | .scrapy 83 | 84 | # Sphinx documentation 85 | docs/_build/ 86 | doc/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # IPython 95 | profile_default/ 96 | ipython_config.py 97 | 98 | # pyenv 99 | .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 109 | __pypackages__/ 110 | 111 | # Celery stuff 112 | celerybeat-schedule 113 | celerybeat.pid 114 | 115 | # SageMath parsed files 116 | *.sage.py 117 | 118 | # Environments 119 | .env 120 | .venv 121 | env/ 122 | venv/ 123 | ENV/ 124 | env.bak/ 125 | venv.bak/ 126 | pythonenv* 127 | 128 | # Spyder project settings 129 | .spyderproject 130 | .spyproject 131 | 132 | # Rope project settings 133 | .ropeproject 134 | 135 | # mkdocs documentation 136 | /site 137 | 138 | # mypy 139 | .mypy_cache/ 140 | .dmypy.json 141 | dmypy.json 142 | 143 | # Pyre type checker 144 | .pyre/ 145 | 146 | # pytype static type analyzer 147 | .pytype/ 148 | 149 | # profiling data 150 | .prof 151 | 152 | ### Linux ### 153 | *~ 154 | 155 | # temporary files which can be created if a process still has a handle open of a deleted file 156 | .fuse_hidden* 157 | 158 | # KDE directory preferences 159 | .directory 160 | 161 | # Linux trash folder which might appear on any partition or disk 162 | .Trash-* 163 | 164 | # .nfs files are created when an open file is removed but is still being accessed 165 | .nfs* 166 | 167 | ### macOS ### 168 | # General 169 | .DS_Store 170 | .AppleDouble 171 | .LSOverride 172 | 173 | # Icon must end with two \r 174 | Icon 175 | 176 | 177 | # Thumbnails 178 | ._* 179 | 180 | # Files that might appear in the root of a volume 181 | .DocumentRevisions-V100 182 | .fseventsd 183 | .Spotlight-V100 184 | .TemporaryItems 185 | .Trashes 186 | .VolumeIcon.icns 187 | .com.apple.timemachine.donotpresent 188 | 189 | # Directories potentially created on remote AFP share 190 | .AppleDB 191 | .AppleDesktop 192 | Network Trash Folder 193 | Temporary Items 194 | .apdisk 195 | 196 | ### Python ### 197 | # Byte-compiled / optimized / DLL files 198 | 199 | # C extensions 200 | 201 | # Distribution / packaging 202 | 203 | # PyInstaller 204 | # Usually these files are written by a python script from a template 205 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 206 | 207 | # Installer logs 208 | 209 | # Unit test / coverage reports 210 | 211 | # Translations 212 | 213 | # Django stuff: 214 | 215 | # Flask stuff: 216 | 217 | # Scrapy stuff: 218 | 219 | # Sphinx documentation 220 | 221 | # PyBuilder 222 | 223 | # Jupyter Notebook 224 | 225 | # IPython 226 | 227 | # pyenv 228 | 229 | # pipenv 230 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 231 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 232 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 233 | # install all needed dependencies. 234 | 235 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 236 | 237 | # Celery stuff 238 | 239 | # SageMath parsed files 240 | 241 | # Environments 242 | 243 | # Spyder project settings 244 | 245 | # Rope project settings 246 | 247 | # mkdocs documentation 248 | 249 | # mypy 250 | 251 | # Pyre type checker 252 | 253 | # pytype static type analyzer 254 | 255 | # profiling data 256 | 257 | ### Vim ### 258 | # Swap 259 | [._]*.s[a-v][a-z] 260 | !*.svg # comment out if you don't need vector files 261 | [._]*.sw[a-p] 262 | [._]s[a-rt-v][a-z] 263 | [._]ss[a-gi-z] 264 | [._]sw[a-p] 265 | 266 | # Session 267 | Session.vim 268 | Sessionx.vim 269 | 270 | # Temporary 271 | .netrwhist 272 | # Auto-generated tag files 273 | tags 274 | # Persistent undo 275 | [._]*.un~ 276 | 277 | ### vscode ### 278 | .vscode/* 279 | !.vscode/settings.json 280 | !.vscode/tasks.json 281 | !.vscode/launch.json 282 | !.vscode/extensions.json 283 | *.code-workspace 284 | 285 | ### Windows ### 286 | # Windows thumbnail cache files 287 | Thumbs.db 288 | Thumbs.db:encryptable 289 | ehthumbs.db 290 | ehthumbs_vista.db 291 | 292 | # Dump file 293 | *.stackdump 294 | 295 | # Folder config file 296 | [Dd]esktop.ini 297 | 298 | # Recycle Bin used on file shares 299 | $RECYCLE.BIN/ 300 | 301 | # Windows Installer files 302 | *.cab 303 | *.msi 304 | *.msix 305 | *.msm 306 | *.msp 307 | 308 | # Windows shortcuts 309 | *.lnk 310 | 311 | # End of https://www.toptal.com/developers/gitignore/api/vim,macos,windows,linux,vscode,python,django 312 | 313 | .vscode 314 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "asgiref" 3 | version = "3.3.1" 4 | description = "ASGI specs, helper code, and adapters" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.5" 8 | 9 | [package.extras] 10 | tests = ["pytest", "pytest-asyncio"] 11 | 12 | [[package]] 13 | name = "django" 14 | version = "3.1.3" 15 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 16 | category = "main" 17 | optional = false 18 | python-versions = ">=3.6" 19 | 20 | [package.dependencies] 21 | asgiref = ">=3.2.10,<4" 22 | pytz = "*" 23 | sqlparse = ">=0.2.2" 24 | 25 | [package.extras] 26 | argon2 = ["argon2-cffi (>=16.1.0)"] 27 | bcrypt = ["bcrypt"] 28 | 29 | [[package]] 30 | name = "django-cors-headers" 31 | version = "3.5.0" 32 | description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." 33 | category = "main" 34 | optional = false 35 | python-versions = ">=3.5" 36 | 37 | [package.dependencies] 38 | Django = ">=2.2" 39 | 40 | [[package]] 41 | name = "djangorestframework" 42 | version = "3.12.2" 43 | description = "Web APIs for Django, made easy." 44 | category = "main" 45 | optional = false 46 | python-versions = ">=3.5" 47 | 48 | [package.dependencies] 49 | django = ">=2.2" 50 | 51 | [[package]] 52 | name = "drf-nested-routers" 53 | version = "0.92.5" 54 | description = "Nested resources for the Django Rest Framework" 55 | category = "main" 56 | optional = false 57 | python-versions = ">=3.5" 58 | 59 | [package.dependencies] 60 | Django = ">=1.11" 61 | djangorestframework = ">=3.6.0" 62 | 63 | [[package]] 64 | name = "flake8" 65 | version = "3.8.4" 66 | description = "the modular source code checker: pep8 pyflakes and co" 67 | category = "dev" 68 | optional = false 69 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 70 | 71 | [package.dependencies] 72 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 73 | mccabe = ">=0.6.0,<0.7.0" 74 | pycodestyle = ">=2.6.0a1,<2.7.0" 75 | pyflakes = ">=2.2.0,<2.3.0" 76 | 77 | [[package]] 78 | name = "importlib-metadata" 79 | version = "3.1.0" 80 | description = "Read metadata from Python packages" 81 | category = "dev" 82 | optional = false 83 | python-versions = ">=3.6" 84 | 85 | [package.dependencies] 86 | zipp = ">=0.5" 87 | 88 | [package.extras] 89 | docs = ["sphinx", "rst.linker"] 90 | testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"] 91 | 92 | [[package]] 93 | name = "mccabe" 94 | version = "0.6.1" 95 | description = "McCabe checker, plugin for flake8" 96 | category = "dev" 97 | optional = false 98 | python-versions = "*" 99 | 100 | [[package]] 101 | name = "psycopg2" 102 | version = "2.8.6" 103 | description = "psycopg2 - Python-PostgreSQL Database Adapter" 104 | category = "main" 105 | optional = false 106 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 107 | 108 | [[package]] 109 | name = "pycodestyle" 110 | version = "2.6.0" 111 | description = "Python style guide checker" 112 | category = "dev" 113 | optional = false 114 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 115 | 116 | [[package]] 117 | name = "pyflakes" 118 | version = "2.2.0" 119 | description = "passive checker of Python programs" 120 | category = "dev" 121 | optional = false 122 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 123 | 124 | [[package]] 125 | name = "pytz" 126 | version = "2020.4" 127 | description = "World timezone definitions, modern and historical" 128 | category = "main" 129 | optional = false 130 | python-versions = "*" 131 | 132 | [[package]] 133 | name = "sqlparse" 134 | version = "0.4.1" 135 | description = "A non-validating SQL parser." 136 | category = "main" 137 | optional = false 138 | python-versions = ">=3.5" 139 | 140 | [[package]] 141 | name = "zipp" 142 | version = "3.4.0" 143 | description = "Backport of pathlib-compatible object wrapper for zip files" 144 | category = "dev" 145 | optional = false 146 | python-versions = ">=3.6" 147 | 148 | [package.extras] 149 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 150 | testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 151 | 152 | [metadata] 153 | lock-version = "1.1" 154 | python-versions = "^3.6" 155 | content-hash = "385341b8feb1d449427b017b3786e2d61d5287326b9fd54f38224aba3ce9765b" 156 | 157 | [metadata.files] 158 | asgiref = [ 159 | {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, 160 | {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, 161 | ] 162 | django = [ 163 | {file = "Django-3.1.3-py3-none-any.whl", hash = "sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927"}, 164 | {file = "Django-3.1.3.tar.gz", hash = "sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f"}, 165 | ] 166 | django-cors-headers = [ 167 | {file = "django-cors-headers-3.5.0.tar.gz", hash = "sha256:db82b2840f667d47872ae3e4a4e0a0d72fbecb42779b8aa233fa8bb965f7836a"}, 168 | {file = "django_cors_headers-3.5.0-py3-none-any.whl", hash = "sha256:9322255c296d5f75089571f29e520c83ff9693df17aa3cf9f6a4bea7c6740169"}, 169 | ] 170 | djangorestframework = [ 171 | {file = "djangorestframework-3.12.2-py3-none-any.whl", hash = "sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7"}, 172 | ] 173 | drf-nested-routers = [ 174 | {file = "drf-nested-routers-0.92.5.tar.gz", hash = "sha256:9d6a326333e9b16549e63b893c27075d98bbc217cd05fb10bbfa770d728d3f66"}, 175 | {file = "drf_nested_routers-0.92.5-py2.py3-none-any.whl", hash = "sha256:995b831be036911e330a86b7129dc6589de62f2a53d72067e7ce2b4a90f28c35"}, 176 | ] 177 | flake8 = [ 178 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 179 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 180 | ] 181 | importlib-metadata = [ 182 | {file = "importlib_metadata-3.1.0-py2.py3-none-any.whl", hash = "sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175"}, 183 | {file = "importlib_metadata-3.1.0.tar.gz", hash = "sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099"}, 184 | ] 185 | mccabe = [ 186 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 187 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 188 | ] 189 | psycopg2 = [ 190 | {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, 191 | {file = "psycopg2-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5"}, 192 | {file = "psycopg2-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad"}, 193 | {file = "psycopg2-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3"}, 194 | {file = "psycopg2-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821"}, 195 | {file = "psycopg2-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301"}, 196 | {file = "psycopg2-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a"}, 197 | {file = "psycopg2-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d"}, 198 | {file = "psycopg2-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84"}, 199 | {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, 200 | {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, 201 | {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, 202 | {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, 203 | ] 204 | pycodestyle = [ 205 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 206 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 207 | ] 208 | pyflakes = [ 209 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 210 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 211 | ] 212 | pytz = [ 213 | {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, 214 | {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, 215 | ] 216 | sqlparse = [ 217 | {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, 218 | {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, 219 | ] 220 | zipp = [ 221 | {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, 222 | {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, 223 | ] 224 | --------------------------------------------------------------------------------