├── .gitignore ├── README.md ├── docker-compose.yml └── five_minutes ├── Dockerfile ├── Dockerfile_local ├── __init__.py ├── events ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_event_description.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests │ ├── __init__.py │ └── factories.py ├── urls.py └── views.py ├── five_minutes ├── __init__.py ├── management │ └── commands │ │ ├── __init__.py │ │ ├── create_tickets.py │ │ ├── init_project.py │ │ └── invalidate_cache.py ├── serializers.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── dev.py │ └── prod.py ├── templates │ ├── base.html │ └── registration │ │ └── login.html ├── urls.py └── wsgi.py ├── manage.py ├── promoters ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests │ ├── __init__.py │ └── factories.py ├── urls.py └── views.py ├── requirements.txt ├── run_local.sh ├── tickets ├── __init__.py ├── admin.py ├── apps.py ├── filters.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_ticket_user.py │ ├── 0003_auto_20190917_2002.py │ ├── 0004_auto_20190923_0827.py │ ├── 0005_auto_20190923_1631.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests │ ├── __init__.py │ └── factories.py ├── urls.py └── views.py ├── tox.ini └── users ├── __init__.py ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20190917_2002.py └── __init__.py ├── models.py ├── serializers.py ├── tests ├── __init__.py └── factories.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/* 3 | .env 4 | data-five-minutes -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taking your API to the next level - DjangoCon2019 2 | 3 |
4 |
5 |
Your username and password didn't match. Please try again.
7 | {% endif %} 8 | 9 | {% if next %} 10 | {% if user.is_authenticated %} 11 |Your account doesn't have access to this page. To proceed, 12 | please login with an account that has access.
13 | {% else %} 14 |Please login to see this page.
15 | {% endif %} 16 | {% endif %} 17 | 18 | 34 | 35 | {# Assumes you setup the password_reset view in your URLconf #} 36 | 37 | 38 | {% endblock %} -------------------------------------------------------------------------------- /five_minutes/five_minutes/urls.py: -------------------------------------------------------------------------------- 1 | """five_minutes URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/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.conf.urls import url 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from rest_framework_jwt.views import obtain_jwt_token 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | path('accounts/', include('django.contrib.auth.urls')), 24 | url(r'^api-token-auth/', obtain_jwt_token), 25 | url(r'^', include('events.urls')), 26 | url(r'^', include('promoters.urls')), 27 | url(r'^', include('tickets.urls')), 28 | url(r'^', include('users.urls')), 29 | ] 30 | -------------------------------------------------------------------------------- /five_minutes/five_minutes/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for five_minutes 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', 'five_minutes.settings.prod') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /five_minutes/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', 'five_minutes.settings.dev') 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 | -------------------------------------------------------------------------------- /five_minutes/promoters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/promoters/__init__.py -------------------------------------------------------------------------------- /five_minutes/promoters/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /five_minutes/promoters/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PromotorsConfig(AppConfig): 5 | name = 'promotors' 6 | -------------------------------------------------------------------------------- /five_minutes/promoters/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-17 16:50 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 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Promoter', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=140)), 20 | ('is_active', models.BooleanField(default=True)), 21 | ('contact_name', models.CharField(max_length=140)), 22 | ('contact_phone', models.CharField(max_length=32)), 23 | ('website', models.URLField(max_length=140)), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='PromoterSpace', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('name', models.CharField(max_length=140)), 31 | ('capacity', models.PositiveIntegerField(default=0)), 32 | ('description', models.TextField()), 33 | ('promoter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='spaces', to='promoters.Promoter')), 34 | ], 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /five_minutes/promoters/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/promoters/migrations/__init__.py -------------------------------------------------------------------------------- /five_minutes/promoters/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Promoter(models.Model): 5 | name = models.CharField(max_length=140) 6 | is_active = models.BooleanField(default=True) 7 | contact_name = models.CharField(max_length=140) 8 | contact_phone = models.CharField(max_length=32) 9 | website = models.URLField(max_length=140) 10 | 11 | 12 | class PromoterSpace(models.Model): 13 | name = models.CharField(max_length=140) 14 | promoter = models.ForeignKey(Promoter, on_delete=models.CASCADE, related_name='spaces') 15 | capacity = models.PositiveIntegerField(default=0) 16 | description = models.TextField() 17 | -------------------------------------------------------------------------------- /five_minutes/promoters/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from five_minutes.serializers import DynamicModelSerializer 4 | from .models import Promoter, PromoterSpace 5 | 6 | 7 | class PromoterSerializer(DynamicModelSerializer): 8 | 9 | class Meta: 10 | model = Promoter 11 | fields = '__all__' 12 | 13 | @staticmethod 14 | def get_nested_fields(): 15 | return 'id', 'name' 16 | 17 | 18 | class PromoterSpaceSerializer(DynamicModelSerializer): 19 | promoter_id = serializers.PrimaryKeyRelatedField(source='promoter', queryset=Promoter.objects.all()) 20 | promoter = PromoterSerializer(read_only=True, fields=PromoterSerializer.get_nested_fields()) 21 | 22 | class Meta: 23 | model = PromoterSpace 24 | fields = ( 25 | 'id', 26 | 'name', 27 | 'promoter_id', 28 | 'promoter', 29 | 'capacity', 30 | 'description', 31 | ) 32 | 33 | @staticmethod 34 | def get_location_fields(): 35 | return 'name', 'description' 36 | -------------------------------------------------------------------------------- /five_minutes/promoters/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/promoters/tests/__init__.py -------------------------------------------------------------------------------- /five_minutes/promoters/tests/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | 3 | 4 | from promoters.models import Promoter, PromoterSpace 5 | 6 | 7 | class PromoterFactory(factory.DjangoModelFactory): 8 | 9 | class Meta: 10 | model = Promoter 11 | 12 | name = factory.Sequence(lambda n: "Promoter %03d" % n) 13 | contact_name = factory.Faker('name') 14 | contact_phone = factory.Faker('phone_number') 15 | website = factory.Faker('uri') 16 | 17 | 18 | class PromoterSpaceFactory(factory.DjangoModelFactory): 19 | 20 | class Meta: 21 | model = PromoterSpace 22 | 23 | name = factory.Sequence(lambda n: "Promoter Space %03d" % n) 24 | promoter = factory.SubFactory(PromoterFactory) 25 | capacity = factory.Iterator([100, 200, 500, 1000]) 26 | description = factory.Faker('paragraphs', nb=3, ext_word_list=None) 27 | -------------------------------------------------------------------------------- /five_minutes/promoters/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLs necessary for the Promoters Application 3 | """ 4 | from django.conf.urls import url, include 5 | from rest_framework.routers import DefaultRouter 6 | 7 | from promoters import views 8 | 9 | # Create a router and register our viewsets with it. 10 | ROUTER = DefaultRouter(trailing_slash=False) 11 | ROUTER.register(r'promoters', views.PromoterViewSet) 12 | ROUTER.register(r'promoter-spaces', views.PromoterSpaceViewSet) 13 | 14 | urlpatterns = [ 15 | url(r'^', include(ROUTER.urls)), 16 | ] 17 | -------------------------------------------------------------------------------- /five_minutes/promoters/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAuthenticated 2 | from rest_framework.viewsets import ModelViewSet 3 | from url_filter.integrations.drf import DjangoFilterBackend 4 | 5 | from .models import Promoter, PromoterSpace 6 | from .serializers import PromoterSerializer, PromoterSpaceSerializer 7 | 8 | 9 | class PromoterViewSet(ModelViewSet): 10 | queryset = Promoter.objects.all() 11 | serializer_class = PromoterSerializer 12 | permission_classes = [IsAuthenticated, ] 13 | filter_backends = [DjangoFilterBackend] 14 | filter_fields = ['id', 'name', 'is_active', 'contact_name', ] 15 | 16 | 17 | class PromoterSpaceViewSet(ModelViewSet): 18 | queryset = PromoterSpace.objects.all() 19 | serializer_class = PromoterSpaceSerializer 20 | permission_classes = [IsAuthenticated, ] 21 | filter_backends = [DjangoFilterBackend] 22 | filter_fields = ['id', 'name', 'promoter', 'capacity', 'description'] 23 | -------------------------------------------------------------------------------- /five_minutes/requirements.txt: -------------------------------------------------------------------------------- 1 | atomicwrites==1.3.0 2 | attrs==19.1.0 3 | backcall==0.1.0 4 | cached-property==1.5.1 5 | coverage==4.5.4 6 | decorator==4.4.0 7 | Django==2.2.5 8 | django-cacheops==4.2 9 | django-extensions==2.2.1 10 | django-redis==4.10.0 11 | django-rest-framework==0.1.0 12 | django-url-filter==0.3.13 13 | djangorestframework==3.10.3 14 | djangorestframework-jwt==1.11.0 15 | drf-renderer-xlsx==0.3.3 16 | dry-rest-permissions==0.1.10 17 | entrypoints==0.3 18 | enum-compat==0.0.2 19 | et-xmlfile==1.0.1 20 | factory-boy==2.12.0 21 | Faker==2.0.2 22 | flake8==3.7.8 23 | funcy==1.13 24 | importlib-metadata==0.23 25 | ipython==7.8.0 26 | ipython-genutils==0.2.0 27 | jdcal==1.4.1 28 | jedi==0.15.1 29 | mccabe==0.6.1 30 | more-itertools==7.2.0 31 | openpyxl==2.6.3 32 | packaging==19.1 33 | parso==0.5.1 34 | pexpect==4.7.0 35 | pickleshare==0.7.5 36 | pluggy==0.13.0 37 | prompt-toolkit==2.0.9 38 | psycopg2-binary==2.7.7 39 | ptyprocess==0.6.0 40 | py==1.8.0 41 | pycodestyle==2.5.0 42 | pyflakes==2.1.1 43 | Pygments==1.6 44 | PyJWT==1.7.1 45 | pyparsing==2.4.2 46 | pytest==5.1.2 47 | pytest-cov==2.7.1 48 | pytest-django==3.5.1 49 | python-dateutil==2.8.0 50 | pytz==2019.2 51 | redis==3.3.8 52 | six==1.12.0 53 | sqlformatter==1.3 54 | sqlparse==0.1.11 55 | text-unidecode==1.3 56 | traitlets==4.3.2 57 | wcwidth==0.1.7 58 | zipp==0.6.0 59 | -------------------------------------------------------------------------------- /five_minutes/run_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sleep 5s 3 | python manage.py migrate --settings=five_minutes.settings.dev 4 | python manage.py runserver 0.0.0.0:8080 5 | -------------------------------------------------------------------------------- /five_minutes/tickets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/tickets/__init__.py -------------------------------------------------------------------------------- /five_minutes/tickets/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /five_minutes/tickets/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TicketsConfig(AppConfig): 5 | name = 'tickets' 6 | -------------------------------------------------------------------------------- /five_minutes/tickets/filters.py: -------------------------------------------------------------------------------- 1 | from url_filter.filtersets import ModelFilterSet 2 | from .models import Ticket 3 | 4 | 5 | class TicketFilterSet(ModelFilterSet): 6 | 7 | class Meta: 8 | model = Ticket 9 | fields = ( 10 | 'id', 11 | 'event', 12 | 'user', 13 | 'already_used', 14 | ) 15 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-17 16:50 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('events', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Ticket', 19 | fields=[ 20 | ('id', models.UUIDField(db_index=True, default=uuid.UUID('0b9674ee-3469-4dd3-b48b-55ab6e76a5b6'), editable=False, primary_key=True, serialize=False)), 21 | ('already_used', models.BooleanField(default=False)), 22 | ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_tickets', to='events.Event')), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/0002_ticket_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-17 16:50 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('tickets', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='ticket', 20 | name='user', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_tickets', to=settings.AUTH_USER_MODEL), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/0003_auto_20190917_2002.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-18 01:02 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('tickets', '0002_ticket_user'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='ticket', 16 | name='id', 17 | field=models.UUIDField(db_index=True, default=uuid.UUID('cd94a771-9b2d-45a8-932d-9ce52efbac04'), editable=False, primary_key=True, serialize=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/0004_auto_20190923_0827.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-23 13:27 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('tickets', '0003_auto_20190917_2002'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='ticket', 16 | name='id', 17 | field=models.UUIDField(db_index=True, default=uuid.UUID('949caf5a-ee35-4ea7-a723-67ad625ccd60'), editable=False, primary_key=True, serialize=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/0005_auto_20190923_1631.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-23 21:31 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('tickets', '0004_auto_20190923_0827'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='ticket', 16 | options={'permissions': [('can_mark_used_ticket', 'Can mark as used a Ticket')]}, 17 | ), 18 | migrations.AlterField( 19 | model_name='ticket', 20 | name='id', 21 | field=models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /five_minutes/tickets/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/tickets/migrations/__init__.py -------------------------------------------------------------------------------- /five_minutes/tickets/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from django.db import models 3 | 4 | from events.models import Event 5 | from users.models import User 6 | 7 | 8 | class Ticket(models.Model): 9 | id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, db_index=True) 10 | event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='event_tickets') 11 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_tickets') 12 | already_used = models.BooleanField(default=False) 13 | 14 | class Meta: 15 | permissions = [('can_mark_used_ticket', 'Can mark as used a Ticket')] 16 | 17 | @staticmethod 18 | def has_read_permission(request): 19 | return True 20 | 21 | def has_object_read_permission(self, request): 22 | # return True 23 | return request.user == self.user 24 | 25 | @staticmethod 26 | def has_write_permission(request): 27 | return True 28 | 29 | @staticmethod 30 | def has_create_permission(request): 31 | return True 32 | 33 | def has_object_write_permission(self, request): 34 | # return True 35 | return request.user.has_perm('ticket.can_mark_used_ticket') 36 | 37 | def has_object_mark_used_ticket_permission(self, request): 38 | return request.user.has_perm('ticket.can_mark_used_ticket') 39 | -------------------------------------------------------------------------------- /five_minutes/tickets/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import SAFE_METHODS, BasePermission 2 | 3 | 4 | class CanUseTicketPermission(BasePermission): 5 | def has_permission(self, request, view): 6 | if request.method in SAFE_METHODS: 7 | return True 8 | return request.user.has_perm('ticket.can_mark_used_ticket') 9 | -------------------------------------------------------------------------------- /five_minutes/tickets/serializers.py: -------------------------------------------------------------------------------- 1 | from dry_rest_permissions.generics import DRYPermissionsField 2 | from rest_framework import serializers 3 | from rest_framework.serializers import ModelSerializer, Serializer 4 | 5 | from events.models import Event 6 | from events.serializers import EventSerializer 7 | from users.models import User 8 | from users.serializers import UserSerializer 9 | from .models import Ticket 10 | 11 | 12 | class TicketSerializer(ModelSerializer): 13 | event_id = serializers.PrimaryKeyRelatedField(source='event', queryset=Event.objects.all()) 14 | event = EventSerializer(read_only=True, fields=EventSerializer.get_nested_fields()) 15 | user_id = serializers.PrimaryKeyRelatedField(source='user', queryset=User.objects.all()) 16 | user = UserSerializer(read_only=True) 17 | permissions = DRYPermissionsField() 18 | 19 | class Meta: 20 | model = Ticket 21 | fields = ( 22 | 'id', 23 | 'event_id', 24 | 'event', 25 | 'user_id', 26 | 'user', 27 | 'already_used', 28 | 'permissions', 29 | ) 30 | 31 | 32 | class MyTicketsSerializer(Serializer): 33 | user = UserSerializer(read_only=True) 34 | tickets = TicketSerializer(many=True, read_only=True) 35 | 36 | def update(self, instance, validated_data): 37 | pass 38 | 39 | def create(self, validated_data): 40 | pass 41 | -------------------------------------------------------------------------------- /five_minutes/tickets/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/tickets/tests/__init__.py -------------------------------------------------------------------------------- /five_minutes/tickets/tests/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | 3 | from events.tests.factories import EventFactory 4 | from tickets.models import Ticket 5 | from users.tests.factories import UserFactory 6 | 7 | 8 | class TicketFactory(factory.DjangoModelFactory): 9 | class Meta: 10 | model = Ticket 11 | 12 | id = factory.Faker('uuid4') 13 | event = factory.SubFactory(EventFactory) 14 | user = factory.SubFactory(UserFactory) 15 | -------------------------------------------------------------------------------- /five_minutes/tickets/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLs necessary for the Tickets Application 3 | """ 4 | from django.conf.urls import url, include 5 | from rest_framework.routers import DefaultRouter 6 | 7 | from tickets import views 8 | 9 | # Create a router and register our viewsets with it. 10 | ROUTER = DefaultRouter(trailing_slash=False) 11 | ROUTER.register(r'tickets', views.TicketViewSet) 12 | ROUTER.register(r'use-ticket', views.UserTicketViewSet) 13 | 14 | urlpatterns = [ 15 | url(r'^', include(ROUTER.urls)), 16 | url(r'my-tickets', views.MyTicketsView.as_view(), name='my-tickets'), 17 | ] 18 | -------------------------------------------------------------------------------- /five_minutes/tickets/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Prefetch 2 | from drf_renderer_xlsx.mixins import XLSXFileMixin 3 | from drf_renderer_xlsx.renderers import XLSXRenderer 4 | from dry_rest_permissions.generics import DRYPermissions 5 | from rest_framework import mixins 6 | from rest_framework.decorators import action 7 | from rest_framework.filters import OrderingFilter 8 | from rest_framework.permissions import IsAuthenticated 9 | from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer 10 | from rest_framework.response import Response 11 | from rest_framework.views import APIView 12 | from rest_framework.viewsets import ModelViewSet, GenericViewSet 13 | from url_filter.integrations.drf import DjangoFilterBackend 14 | 15 | from events.models import Event 16 | from promoters.models import PromoterSpace 17 | from tickets.filters import TicketFilterSet 18 | from tickets.permissions import CanUseTicketPermission 19 | from .models import Ticket 20 | from .serializers import TicketSerializer, MyTicketsSerializer 21 | 22 | 23 | class TicketViewSet(XLSXFileMixin, ModelViewSet): 24 | queryset = Ticket.objects.all() 25 | serializer_class = TicketSerializer 26 | permission_classes = (IsAuthenticated, DRYPermissions,) 27 | renderer_classes = (BrowsableAPIRenderer, JSONRenderer, XLSXRenderer,) 28 | filter_backends = (DjangoFilterBackend, OrderingFilter) 29 | filter_class = TicketFilterSet 30 | ordering_fields = ( 31 | 'event__name', 32 | 'event_start_datetime', 33 | ) 34 | 35 | def get_queryset(self): 36 | return Ticket.objects.all() \ 37 | .prefetch_related('user') \ 38 | .prefetch_related( 39 | Prefetch( 40 | 'event', 41 | queryset=Event.objects.all().only('id', 'name', 'start_datetime', 'end_datetime', 'space').cache() 42 | ) 43 | ) \ 44 | .prefetch_related( 45 | Prefetch( 46 | 'event__space', 47 | queryset=PromoterSpace.objects.all().only('id', 'name', 'description').cache() 48 | ) 49 | ).cache() 50 | 51 | @action(methods=['post', ], detail=True, url_path="mark-used-ticket") 52 | def mark_used_ticket(self, request, *args, **kwargs): 53 | instance = self.get_object() 54 | instance.already_used = True 55 | instance.save() 56 | serializer = self.get_serializer(instance) 57 | return Response(serializer.data) 58 | 59 | 60 | class MyTicketsView(APIView): 61 | 62 | def get(self, request, *args, **kwargs): 63 | user = request.user 64 | tickets = Ticket.objects.filter(user=user).cache() 65 | return Response(MyTicketsSerializer({'user': user, 'tickets': tickets}, context={'request': request}).data) 66 | 67 | 68 | class UserTicketViewSet(mixins.RetrieveModelMixin, GenericViewSet): 69 | queryset = Ticket.objects.all() 70 | serializer_class = TicketSerializer 71 | permission_classes = (IsAuthenticated, DRYPermissions, CanUseTicketPermission) 72 | 73 | def get_queryset(self): 74 | return Ticket.objects.all() \ 75 | .prefetch_related('user') \ 76 | .prefetch_related( 77 | Prefetch( 78 | 'event', 79 | queryset=Event.objects.all().only('id', 'name', 'start_datetime', 'end_datetime', 'space').cache() 80 | ) 81 | ) \ 82 | .prefetch_related( 83 | Prefetch( 84 | 'event__space', 85 | queryset=PromoterSpace.objects.all().only('id', 'name', 'description').cache() 86 | ) 87 | ).cache() 88 | -------------------------------------------------------------------------------- /five_minutes/tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # SyntaxError: invalid syntax for f'' formats 3 | ignore = E999,W504 4 | max-line-length = 120 5 | exclude = */docs/*,*/migrations/*,five_minutes/settings/*,manage.py,five_minutes/s3utils.py 6 | max-complexity = 15 -------------------------------------------------------------------------------- /five_minutes/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/users/__init__.py -------------------------------------------------------------------------------- /five_minutes/users/admin.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.contrib.auth.models import Permission 5 | from django.contrib import admin 6 | 7 | from .models import User 8 | 9 | 10 | @admin.register(User) 11 | class UserAdmin(DjangoUserAdmin): 12 | """Define admin model for custom User model with no email field.""" 13 | 14 | fieldsets = ( 15 | (None, {'fields': ('email', 'password')}), 16 | (_('Personal info'), {'fields': ('first_name', 'last_name')}), 17 | (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 18 | 'groups', 'user_permissions')}), 19 | (_('Important dates'), {'fields': ('last_login', 'date_joined')}), 20 | ) 21 | add_fieldsets = ( 22 | (None, { 23 | 'classes': ('wide',), 24 | 'fields': ('email', 'password1', 'password2'), 25 | }), 26 | ) 27 | list_display = ('email', 'first_name', 'last_name', 'is_staff') 28 | search_fields = ('email', 'first_name', 'last_name') 29 | ordering = ('email',) 30 | 31 | 32 | admin.site.register(Permission) 33 | -------------------------------------------------------------------------------- /five_minutes/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /five_minutes/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-17 16:50 2 | 3 | import django.contrib.auth.models 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0011_update_proxy_permissions'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('password', models.CharField(max_length=128, verbose_name='password')), 22 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 23 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 24 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 25 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 26 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 27 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 28 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 29 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 30 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 31 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 32 | ], 33 | options={ 34 | 'verbose_name': 'user', 35 | 'verbose_name_plural': 'users', 36 | 'abstract': False, 37 | }, 38 | managers=[ 39 | ('objects', django.contrib.auth.models.UserManager()), 40 | ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /five_minutes/users/migrations/0002_auto_20190917_2002.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-18 01:02 2 | 3 | from django.db import migrations 4 | import users.models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelManagers( 15 | name='user', 16 | managers=[ 17 | ('objects', users.models.UserManager()), 18 | ], 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /five_minutes/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/users/migrations/__init__.py -------------------------------------------------------------------------------- /five_minutes/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser, BaseUserManager 2 | from django.db import models 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | 6 | class UserManager(BaseUserManager): 7 | """Define a model manager for User model with no username field.""" 8 | 9 | use_in_migrations = True 10 | 11 | def _create_user(self, email, password, **extra_fields): 12 | """Create and save a User with the given email and password.""" 13 | if not email: 14 | raise ValueError('The given email must be set') 15 | email = self.normalize_email(email) 16 | user = self.model(email=email, **extra_fields) 17 | user.set_password(password) 18 | user.save(using=self._db) 19 | return user 20 | 21 | def create_user(self, email, password=None, **extra_fields): 22 | """Create and save a regular User with the given email and password.""" 23 | extra_fields.setdefault('is_staff', False) 24 | extra_fields.setdefault('is_superuser', False) 25 | return self._create_user(email, password, **extra_fields) 26 | 27 | def create_superuser(self, email, password, **extra_fields): 28 | """Create and save a SuperUser with the given email and password.""" 29 | extra_fields.setdefault('is_staff', True) 30 | extra_fields.setdefault('is_superuser', True) 31 | 32 | if extra_fields.get('is_staff') is not True: 33 | raise ValueError('Superuser must have is_staff=True.') 34 | if extra_fields.get('is_superuser') is not True: 35 | raise ValueError('Superuser must have is_superuser=True.') 36 | 37 | return self._create_user(email, password, **extra_fields) 38 | 39 | 40 | class User(AbstractUser): 41 | """User model.""" 42 | 43 | username = None 44 | email = models.EmailField(_('email address'), unique=True) 45 | 46 | objects = UserManager() 47 | 48 | USERNAME_FIELD = 'email' 49 | REQUIRED_FIELDS = [] 50 | -------------------------------------------------------------------------------- /five_minutes/users/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer 2 | 3 | from .models import User 4 | 5 | 6 | class UserSerializer(ModelSerializer): 7 | 8 | class Meta: 9 | model = User 10 | fields = ( 11 | 'id', 12 | 'email', 13 | 'first_name', 14 | 'last_name', 15 | 'date_joined', 16 | ) 17 | -------------------------------------------------------------------------------- /five_minutes/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosmart626/djangocon-2019/381b4a554383f8068beef288b65d494dc2b5629a/five_minutes/users/tests/__init__.py -------------------------------------------------------------------------------- /five_minutes/users/tests/factories.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import factory 3 | 4 | from users.models import User 5 | 6 | 7 | class UserFactory(factory.DjangoModelFactory): 8 | class Meta: 9 | model = User 10 | 11 | first_name = factory.Sequence(lambda n: 'john%s' % n) 12 | last_name = factory.Faker('last_name') 13 | email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name, o.last_name)) 14 | date_joined = factory.LazyFunction(datetime.datetime.now) 15 | -------------------------------------------------------------------------------- /five_minutes/users/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLs necessary for the Users Application 3 | """ 4 | from django.conf.urls import url, include 5 | from rest_framework.routers import DefaultRouter 6 | 7 | from users import views 8 | 9 | # Create a router and register our viewsets with it. 10 | ROUTER = DefaultRouter(trailing_slash=False) 11 | ROUTER.register(r'users', views.UserViewSet) 12 | 13 | urlpatterns = [ 14 | url(r'^', include(ROUTER.urls)), 15 | ] 16 | -------------------------------------------------------------------------------- /five_minutes/users/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAuthenticated 2 | from rest_framework.viewsets import ModelViewSet 3 | 4 | from .models import User 5 | from .serializers import UserSerializer 6 | 7 | 8 | class UserViewSet(ModelViewSet): 9 | queryset = User.objects.all() 10 | serializer_class = UserSerializer 11 | permission_classes = [IsAuthenticated, ] 12 | --------------------------------------------------------------------------------