├── README.md └── src ├── account ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── admin.cpython-37.pyc │ ├── backends.cpython-37.pyc │ ├── forms.cpython-37.pyc │ ├── models.cpython-37.pyc │ └── views.cpython-37.pyc ├── admin.py ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── serializers.cpython-37.pyc │ │ ├── urls.cpython-37.pyc │ │ └── views.cpython-37.pyc │ ├── serializers.py │ ├── urls.py │ └── views.py ├── apps.py ├── backends.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-37.pyc │ │ ├── 0002_auto_20190617_1641.cpython-37.pyc │ │ └── __init__.cpython-37.pyc ├── models.py ├── templates │ └── account │ │ ├── account.html │ │ ├── login.html │ │ ├── must_authenticate.html │ │ └── register.html ├── tests.py └── views.py ├── blog ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── admin.cpython-37.pyc │ ├── forms.cpython-37.pyc │ ├── models.cpython-37.pyc │ ├── serializers.cpython-37.pyc │ ├── urls-api.cpython-37.pyc │ ├── urls.cpython-37.pyc │ ├── urls_api.cpython-37.pyc │ ├── utils.cpython-37.pyc │ ├── views.cpython-37.pyc │ └── views_api.cpython-37.pyc ├── admin.py ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── serializers.cpython-37.pyc │ │ ├── urls.cpython-37.pyc │ │ └── views.cpython-37.pyc │ ├── serializers.py │ ├── urls.py │ └── views.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-37.pyc │ │ └── __init__.cpython-37.pyc ├── models.py ├── templates │ └── blog │ │ ├── create_blog.html │ │ ├── detail_blog.html │ │ ├── edit_blog.html │ │ └── snippets │ │ ├── blog_post_pagination.html │ │ └── blog_post_snippet.html ├── tests.py ├── urls.py ├── utils.py └── views.py ├── db.sqlite3 ├── manage.py ├── media_cdn └── blog │ ├── 1 │ ├── First_Blog_Post-Filtering_RecyclerView_with_SearchView.png │ ├── Second_Blog_post-4.png │ └── Second_Blog_post-detecting_memory_leaks_thumbnail.png │ ├── 2 │ └── hdhfhgh-11.png │ └── 3 │ └── gdfgdgdfg-Android_Thumbnail.png ├── mysite ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── settings.cpython-37.pyc │ ├── urls.cpython-37.pyc │ └── wsgi.cpython-37.pyc ├── settings.py ├── urls.py └── wsgi.py ├── personal ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── admin.cpython-37.pyc │ ├── models.cpython-37.pyc │ └── views.cpython-37.pyc ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_delete_question.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-37.pyc │ │ ├── 0002_auto_20190617_0949.cpython-37.pyc │ │ ├── 0002_delete_question.cpython-37.pyc │ │ ├── 0003_auto_20190617_0950.cpython-37.pyc │ │ ├── 0004_auto_20190617_0952.cpython-37.pyc │ │ └── __init__.cpython-37.pyc ├── models.py ├── templates │ └── personal │ │ └── home.html ├── tests.py └── views.py ├── requirements.txt ├── static └── codingwithmitch_logo.png ├── static_cdn ├── admin │ ├── css │ │ ├── autocomplete.css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── fonts.css │ │ ├── forms.css │ │ ├── login.css │ │ ├── responsive.css │ │ ├── responsive_rtl.css │ │ ├── rtl.css │ │ ├── vendor │ │ │ └── select2 │ │ │ │ ├── LICENSE-SELECT2.md │ │ │ │ ├── select2.css │ │ │ │ └── select2.min.css │ │ └── widgets.css │ ├── fonts │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── Roboto-Bold-webfont.woff │ │ ├── Roboto-Light-webfont.woff │ │ └── Roboto-Regular-webfont.woff │ ├── img │ │ ├── LICENSE │ │ ├── README.txt │ │ ├── calendar-icons.svg │ │ ├── gis │ │ │ ├── move_vertex_off.svg │ │ │ └── move_vertex_on.svg │ │ ├── icon-addlink.svg │ │ ├── icon-alert.svg │ │ ├── icon-calendar.svg │ │ ├── icon-changelink.svg │ │ ├── icon-clock.svg │ │ ├── icon-deletelink.svg │ │ ├── icon-no.svg │ │ ├── icon-unknown-alt.svg │ │ ├── icon-unknown.svg │ │ ├── icon-viewlink.svg │ │ ├── icon-yes.svg │ │ ├── inline-delete.svg │ │ ├── search.svg │ │ ├── selector-icons.svg │ │ ├── sorting-icons.svg │ │ ├── tooltag-add.svg │ │ └── tooltag-arrowright.svg │ └── js │ │ ├── SelectBox.js │ │ ├── SelectFilter2.js │ │ ├── actions.js │ │ ├── actions.min.js │ │ ├── admin │ │ ├── DateTimeShortcuts.js │ │ └── RelatedObjectLookups.js │ │ ├── autocomplete.js │ │ ├── calendar.js │ │ ├── cancel.js │ │ ├── change_form.js │ │ ├── collapse.js │ │ ├── collapse.min.js │ │ ├── core.js │ │ ├── inlines.js │ │ ├── inlines.min.js │ │ ├── jquery.init.js │ │ ├── popup_response.js │ │ ├── prepopulate.js │ │ ├── prepopulate.min.js │ │ ├── prepopulate_init.js │ │ ├── timeparse.js │ │ ├── urlify.js │ │ └── vendor │ │ ├── jquery │ │ ├── LICENSE.txt │ │ ├── jquery.js │ │ └── jquery.min.js │ │ ├── select2 │ │ ├── LICENSE.md │ │ ├── i18n │ │ │ ├── ar.js │ │ │ ├── az.js │ │ │ ├── bg.js │ │ │ ├── ca.js │ │ │ ├── cs.js │ │ │ ├── da.js │ │ │ ├── de.js │ │ │ ├── el.js │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── et.js │ │ │ ├── eu.js │ │ │ ├── fa.js │ │ │ ├── fi.js │ │ │ ├── fr.js │ │ │ ├── gl.js │ │ │ ├── he.js │ │ │ ├── hi.js │ │ │ ├── hr.js │ │ │ ├── hu.js │ │ │ ├── id.js │ │ │ ├── is.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── km.js │ │ │ ├── ko.js │ │ │ ├── lt.js │ │ │ ├── lv.js │ │ │ ├── mk.js │ │ │ ├── ms.js │ │ │ ├── nb.js │ │ │ ├── nl.js │ │ │ ├── pl.js │ │ │ ├── pt-BR.js │ │ │ ├── pt.js │ │ │ ├── ro.js │ │ │ ├── ru.js │ │ │ ├── sk.js │ │ │ ├── sr-Cyrl.js │ │ │ ├── sr.js │ │ │ ├── sv.js │ │ │ ├── th.js │ │ │ ├── tr.js │ │ │ ├── uk.js │ │ │ ├── vi.js │ │ │ ├── zh-CN.js │ │ │ └── zh-TW.js │ │ ├── select2.full.js │ │ └── select2.full.min.js │ │ └── xregexp │ │ ├── LICENSE.txt │ │ ├── xregexp.js │ │ └── xregexp.min.js └── codingwithmitch_logo.png └── templates ├── base.html ├── registration ├── password_change.html ├── password_change_done.html ├── password_reset_complete.html ├── password_reset_done.html ├── password_reset_email.html ├── password_reset_form.html └── password_reset_subject.txt └── snippets ├── base_css.html ├── footer.html └── header.html /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Build a REST API

3 | 4 |

In this course you'll learn to build a REST API for a website so other technologies can interact with it.

5 |

This course is a continuation of the "Building a website with Django (Python)" course. Which is a free course where I show you how to build a website like open-api.xyz.

6 |

What you'll learn:

7 | 24 | 25 | -------------------------------------------------------------------------------- /src/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__init__.py -------------------------------------------------------------------------------- /src/account/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/__pycache__/admin.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/admin.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/__pycache__/backends.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/backends.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/__pycache__/forms.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/forms.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/__pycache__/models.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/models.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/__pycache__/views.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/__pycache__/views.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from account.models import Account 4 | 5 | 6 | class AccountAdmin(UserAdmin): 7 | list_display = ('pk', 'email','username','date_joined', 'last_login', 'is_admin','is_staff') 8 | search_fields = ('pk', 'email','username',) 9 | readonly_fields=('pk', 'date_joined', 'last_login') 10 | 11 | filter_horizontal = () 12 | list_filter = () 13 | fieldsets = () 14 | 15 | 16 | admin.site.register(Account, AccountAdmin) 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/account/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/api/__init__.py -------------------------------------------------------------------------------- /src/account/api/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/api/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/api/__pycache__/serializers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/api/__pycache__/serializers.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/api/__pycache__/urls.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/api/__pycache__/urls.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/api/__pycache__/views.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/api/__pycache__/views.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from account.models import Account 4 | 5 | 6 | class RegistrationSerializer(serializers.ModelSerializer): 7 | 8 | password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) 9 | 10 | class Meta: 11 | model = Account 12 | fields = ['email', 'username', 'password', 'password2'] 13 | extra_kwargs = { 14 | 'password': {'write_only': True}, 15 | } 16 | 17 | 18 | def save(self): 19 | 20 | account = Account( 21 | email=self.validated_data['email'], 22 | username=self.validated_data['username'] 23 | ) 24 | password = self.validated_data['password'] 25 | password2 = self.validated_data['password2'] 26 | if password != password2: 27 | raise serializers.ValidationError({'password': 'Passwords must match.'}) 28 | account.set_password(password) 29 | account.save() 30 | return account 31 | 32 | 33 | class AccountPropertiesSerializer(serializers.ModelSerializer): 34 | 35 | class Meta: 36 | model = Account 37 | fields = ['pk', 'email', 'username', ] 38 | 39 | 40 | 41 | 42 | class ChangePasswordSerializer(serializers.Serializer): 43 | 44 | old_password = serializers.CharField(required=True) 45 | new_password = serializers.CharField(required=True) 46 | confirm_new_password = serializers.CharField(required=True) 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/account/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from account.api.views import( 3 | registration_view, 4 | ObtainAuthTokenView, 5 | account_properties_view, 6 | update_account_view, 7 | does_account_exist_view, 8 | ChangePasswordView, 9 | ) 10 | from rest_framework.authtoken.views import obtain_auth_token 11 | 12 | app_name = 'account' 13 | 14 | urlpatterns = [ 15 | path('check_if_account_exists/', does_account_exist_view, name="check_if_account_exists"), 16 | path('change_password/', ChangePasswordView.as_view(), name="change_password"), 17 | path('properties', account_properties_view, name="properties"), 18 | path('properties/update', update_account_view, name="update"), 19 | path('login', ObtainAuthTokenView.as_view(), name="login"), 20 | path('register', registration_view, name="register"), 21 | 22 | ] -------------------------------------------------------------------------------- /src/account/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.response import Response 3 | from rest_framework.decorators import api_view, permission_classes 4 | from rest_framework.permissions import IsAuthenticated 5 | from rest_framework.views import APIView 6 | from rest_framework.generics import UpdateAPIView 7 | from django.contrib.auth import authenticate 8 | from rest_framework.authentication import TokenAuthentication 9 | from rest_framework.decorators import api_view, authentication_classes, permission_classes 10 | 11 | from account.api.serializers import RegistrationSerializer, AccountPropertiesSerializer, ChangePasswordSerializer 12 | from account.models import Account 13 | from rest_framework.authtoken.models import Token 14 | 15 | # Register 16 | # Response: https://gist.github.com/mitchtabian/c13c41fa0f51b304d7638b7bac7cb694 17 | # Url: https:///api/account/register 18 | @api_view(['POST', ]) 19 | @permission_classes([]) 20 | @authentication_classes([]) 21 | def registration_view(request): 22 | 23 | if request.method == 'POST': 24 | data = {} 25 | email = request.data.get('email', '0').lower() 26 | if validate_email(email) != None: 27 | data['error_message'] = 'That email is already in use.' 28 | data['response'] = 'Error' 29 | return Response(data) 30 | 31 | username = request.data.get('username', '0') 32 | if validate_username(username) != None: 33 | data['error_message'] = 'That username is already in use.' 34 | data['response'] = 'Error' 35 | return Response(data) 36 | 37 | serializer = RegistrationSerializer(data=request.data) 38 | 39 | if serializer.is_valid(): 40 | account = serializer.save() 41 | data['response'] = 'successfully registered new user.' 42 | data['email'] = account.email 43 | data['username'] = account.username 44 | data['pk'] = account.pk 45 | token = Token.objects.get(user=account).key 46 | data['token'] = token 47 | else: 48 | data = serializer.errors 49 | return Response(data) 50 | 51 | def validate_email(email): 52 | account = None 53 | try: 54 | account = Account.objects.get(email=email) 55 | except Account.DoesNotExist: 56 | return None 57 | if account != None: 58 | return email 59 | 60 | def validate_username(username): 61 | account = None 62 | try: 63 | account = Account.objects.get(username=username) 64 | except Account.DoesNotExist: 65 | return None 66 | if account != None: 67 | return username 68 | 69 | 70 | # Account properties 71 | # Response: https://gist.github.com/mitchtabian/4adaaaabc767df73c5001a44b4828ca5 72 | # Url: https:///api/account/ 73 | # Headers: Authorization: Token 74 | @api_view(['GET', ]) 75 | @permission_classes((IsAuthenticated, )) 76 | def account_properties_view(request): 77 | 78 | try: 79 | account = request.user 80 | except Account.DoesNotExist: 81 | return Response(status=status.HTTP_404_NOT_FOUND) 82 | 83 | if request.method == 'GET': 84 | serializer = AccountPropertiesSerializer(account) 85 | return Response(serializer.data) 86 | 87 | 88 | # Account update properties 89 | # Response: https://gist.github.com/mitchtabian/72bb4c4811199b1d303eb2d71ec932b2 90 | # Url: https:///api/account/properties/update 91 | # Headers: Authorization: Token 92 | @api_view(['PUT',]) 93 | @permission_classes((IsAuthenticated, )) 94 | def update_account_view(request): 95 | 96 | try: 97 | account = request.user 98 | except Account.DoesNotExist: 99 | return Response(status=status.HTTP_404_NOT_FOUND) 100 | 101 | if request.method == 'PUT': 102 | serializer = AccountPropertiesSerializer(account, data=request.data) 103 | data = {} 104 | if serializer.is_valid(): 105 | serializer.save() 106 | data['response'] = 'Account update success' 107 | return Response(data=data) 108 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 109 | 110 | 111 | 112 | # LOGIN 113 | # Response: https://gist.github.com/mitchtabian/8e1bde81b3be342853ddfcc45ec0df8a 114 | # URL: http://127.0.0.1:8000/api/account/login 115 | class ObtainAuthTokenView(APIView): 116 | 117 | authentication_classes = [] 118 | permission_classes = [] 119 | 120 | def post(self, request): 121 | context = {} 122 | 123 | email = request.POST.get('username') 124 | password = request.POST.get('password') 125 | account = authenticate(email=email, password=password) 126 | if account: 127 | try: 128 | token = Token.objects.get(user=account) 129 | except Token.DoesNotExist: 130 | token = Token.objects.create(user=account) 131 | context['response'] = 'Successfully authenticated.' 132 | context['pk'] = account.pk 133 | context['email'] = email.lower() 134 | context['token'] = token.key 135 | else: 136 | context['response'] = 'Error' 137 | context['error_message'] = 'Invalid credentials' 138 | 139 | return Response(context) 140 | 141 | 142 | 143 | 144 | @api_view(['GET', ]) 145 | @permission_classes([]) 146 | @authentication_classes([]) 147 | def does_account_exist_view(request): 148 | 149 | if request.method == 'GET': 150 | email = request.GET['email'].lower() 151 | data = {} 152 | try: 153 | account = Account.objects.get(email=email) 154 | data['response'] = email 155 | except Account.DoesNotExist: 156 | data['response'] = "Account does not exist" 157 | return Response(data) 158 | 159 | 160 | 161 | class ChangePasswordView(UpdateAPIView): 162 | 163 | serializer_class = ChangePasswordSerializer 164 | model = Account 165 | permission_classes = (IsAuthenticated,) 166 | authentication_classes = (TokenAuthentication,) 167 | 168 | def get_object(self, queryset=None): 169 | obj = self.request.user 170 | return obj 171 | 172 | def update(self, request, *args, **kwargs): 173 | self.object = self.get_object() 174 | serializer = self.get_serializer(data=request.data) 175 | 176 | if serializer.is_valid(): 177 | # Check old password 178 | if not self.object.check_password(serializer.data.get("old_password")): 179 | return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST) 180 | 181 | # confirm the new passwords match 182 | new_password = serializer.data.get("new_password") 183 | confirm_new_password = serializer.data.get("confirm_new_password") 184 | if new_password != confirm_new_password: 185 | return Response({"new_password": ["New passwords must match"]}, status=status.HTTP_400_BAD_REQUEST) 186 | 187 | # set_password also hashes the password that the user will get 188 | self.object.set_password(serializer.data.get("new_password")) 189 | self.object.save() 190 | return Response({"response":"successfully changed password"}, status=status.HTTP_200_OK) 191 | 192 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /src/account/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountConfig(AppConfig): 5 | name = 'account' 6 | -------------------------------------------------------------------------------- /src/account/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.backends import ModelBackend 3 | 4 | 5 | class CaseInsensitiveModelBackend(ModelBackend): 6 | def authenticate(self, request, username=None, password=None, **kwargs): 7 | UserModel = get_user_model() 8 | if username is None: 9 | username = kwargs.get(UserModel.USERNAME_FIELD) 10 | try: 11 | case_insensitive_username_field = '{}__iexact'.format(UserModel.USERNAME_FIELD) 12 | user = UserModel._default_manager.get(**{case_insensitive_username_field: username}) 13 | except UserModel.DoesNotExist: 14 | # Run the default password hasher once to reduce the timing 15 | # difference between an existing and a non-existing user (#20760). 16 | UserModel().set_password(password) 17 | else: 18 | if user.check_password(password) and self.user_can_authenticate(user): 19 | return user 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/account/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth import authenticate 4 | 5 | from account.models import Account 6 | 7 | 8 | class RegistrationForm(UserCreationForm): 9 | email = forms.EmailField(max_length=254, help_text='Required. Add a valid email address.') 10 | 11 | class Meta: 12 | model = Account 13 | fields = ('email', 'username', 'password1', 'password2', ) 14 | 15 | def clean_email(self): 16 | email = self.cleaned_data['email'].lower() 17 | try: 18 | account = Account.objects.exclude(pk=self.instance.pk).get(email=email) 19 | except Account.DoesNotExist: 20 | return email 21 | raise forms.ValidationError('Email "%s" is already in use.' % account) 22 | 23 | def clean_username(self): 24 | username = self.cleaned_data['username'] 25 | try: 26 | account = Account.objects.exclude(pk=self.instance.pk).get(username=username) 27 | except Account.DoesNotExist: 28 | return username 29 | raise forms.ValidationError('Username "%s" is already in use.' % username) 30 | 31 | 32 | class AccountAuthenticationForm(forms.ModelForm): 33 | 34 | password = forms.CharField(label='Password', widget=forms.PasswordInput) 35 | 36 | class Meta: 37 | model = Account 38 | fields = ('email', 'password') 39 | 40 | def clean(self): 41 | if self.is_valid(): 42 | email = self.cleaned_data['email'] 43 | password = self.cleaned_data['password'] 44 | if not authenticate(email=email, password=password): 45 | raise forms.ValidationError("Invalid login") 46 | 47 | 48 | class AccountUpdateForm(forms.ModelForm): 49 | 50 | class Meta: 51 | model = Account 52 | fields = ('email', 'username', ) 53 | 54 | def clean_email(self): 55 | email = self.cleaned_data['email'].lower() 56 | try: 57 | account = Account.objects.exclude(pk=self.instance.pk).get(email=email) 58 | except Account.DoesNotExist: 59 | return email 60 | raise forms.ValidationError('Email "%s" is already in use.' % account) 61 | 62 | def clean_username(self): 63 | username = self.cleaned_data['username'] 64 | try: 65 | account = Account.objects.exclude(pk=self.instance.pk).get(username=username) 66 | except Account.DoesNotExist: 67 | return username 68 | raise forms.ValidationError('Username "%s" is already in use.' % username) 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/account/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-24 20:53 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='Account', 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 | ('email', models.EmailField(max_length=60, unique=True, verbose_name='email')), 20 | ('username', models.CharField(max_length=30, unique=True)), 21 | ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), 22 | ('last_login', models.DateTimeField(auto_now=True, verbose_name='last login')), 23 | ('is_admin', models.BooleanField(default=False)), 24 | ('is_active', models.BooleanField(default=True)), 25 | ('is_staff', models.BooleanField(default=False)), 26 | ('is_superuser', models.BooleanField(default=False)), 27 | ], 28 | options={ 29 | 'abstract': False, 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/migrations/__init__.py -------------------------------------------------------------------------------- /src/account/migrations/__pycache__/0001_initial.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/migrations/__pycache__/0001_initial.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/migrations/__pycache__/0002_auto_20190617_1641.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/migrations/__pycache__/0002_auto_20190617_1641.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/migrations/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/account/migrations/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/account/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractBaseUser, BaseUserManager 3 | 4 | from django.conf import settings 5 | from django.db.models.signals import post_save 6 | from django.dispatch import receiver 7 | from rest_framework.authtoken.models import Token 8 | 9 | 10 | class MyAccountManager(BaseUserManager): 11 | def create_user(self, email, username, password=None): 12 | if not email: 13 | raise ValueError('Users must have an email address') 14 | if not username: 15 | raise ValueError('Users must have a username') 16 | 17 | user = self.model( 18 | email=self.normalize_email(email), 19 | username=username, 20 | ) 21 | 22 | user.set_password(password) 23 | user.save(using=self._db) 24 | return user 25 | 26 | def create_superuser(self, email, username, password): 27 | user = self.create_user( 28 | email=self.normalize_email(email), 29 | password=password, 30 | username=username, 31 | ) 32 | user.is_admin = True 33 | user.is_staff = True 34 | user.is_superuser = True 35 | user.save(using=self._db) 36 | return user 37 | 38 | 39 | class Account(AbstractBaseUser): 40 | email = models.EmailField(verbose_name="email", max_length=60, unique=True) 41 | username = models.CharField(max_length=30, unique=True) 42 | date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True) 43 | last_login = models.DateTimeField(verbose_name='last login', auto_now=True) 44 | is_admin = models.BooleanField(default=False) 45 | is_active = models.BooleanField(default=True) 46 | is_staff = models.BooleanField(default=False) 47 | is_superuser = models.BooleanField(default=False) 48 | 49 | 50 | USERNAME_FIELD = 'email' 51 | REQUIRED_FIELDS = ['username'] 52 | 53 | objects = MyAccountManager() 54 | 55 | def __str__(self): 56 | return self.email 57 | 58 | # For checking permissions. to keep it simple all admin have ALL permissons 59 | def has_perm(self, perm, obj=None): 60 | return self.is_admin 61 | 62 | # Does this user have permission to view this app? (ALWAYS YES FOR SIMPLICITY) 63 | def has_module_perms(self, app_label): 64 | return True 65 | 66 | 67 | @receiver(post_save, sender=settings.AUTH_USER_MODEL) 68 | def create_auth_token(sender, instance=None, created=False, **kwargs): 69 | if created: 70 | Token.objects.create(user=instance) 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/account/templates/account/account.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 | 43 | 44 | 45 | 75 | 76 |
77 | Change password 78 |
79 | 80 | 81 |
82 |
83 |
84 |

Blog posts:

85 | {% if blog_posts %} 86 | 93 | {% else %} 94 |

You have no blog posts. Create a post here.

95 | {% endif %} 96 | 97 |
98 |
99 |
100 | 101 | 102 | {% endblock content %} 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/account/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 6 | 38 | 39 | 40 | 67 | 68 |
69 | Reset password 70 |
71 | 72 | 73 | 74 | {% endblock content %} 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/account/templates/account/must_authenticate.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 |
8 |

Must authenticate to view this page.

9 |

10 | Register or 11 | Login 12 |

13 |
14 |
15 | 16 | 17 | {% endblock content %} -------------------------------------------------------------------------------- /src/account/templates/account/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 6 | 7 | 46 | 47 | 48 | 79 | 80 | 81 | {% endblock content %} 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/account/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/account/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib.auth import login, authenticate, logout 3 | from account.forms import RegistrationForm, AccountAuthenticationForm, AccountUpdateForm 4 | 5 | from blog.models import BlogPost 6 | 7 | def registration_view(request): 8 | context = {} 9 | if request.POST: 10 | form = RegistrationForm(request.POST) 11 | if form.is_valid(): 12 | form.save() 13 | email = form.cleaned_data.get('email').lower() 14 | raw_password = form.cleaned_data.get('password1') 15 | account = authenticate(email=email, password=raw_password) 16 | login(request, account) 17 | return redirect('home') 18 | else: 19 | context['registration_form'] = form 20 | 21 | else: 22 | form = RegistrationForm() 23 | context['registration_form'] = form 24 | return render(request, 'account/register.html', context) 25 | 26 | 27 | def logout_view(request): 28 | logout(request) 29 | return redirect('/') 30 | 31 | 32 | def login_view(request): 33 | 34 | context = {} 35 | 36 | user = request.user 37 | if user.is_authenticated: 38 | return redirect("home") 39 | 40 | if request.POST: 41 | form = AccountAuthenticationForm(request.POST) 42 | if form.is_valid(): 43 | email = request.POST['email'] 44 | password = request.POST['password'] 45 | user = authenticate(email=email, password=password) 46 | 47 | if user: 48 | login(request, user) 49 | return redirect("home") 50 | 51 | else: 52 | form = AccountAuthenticationForm() 53 | 54 | context['login_form'] = form 55 | 56 | # print(form) 57 | return render(request, "account/login.html", context) 58 | 59 | 60 | def account_view(request): 61 | 62 | if not request.user.is_authenticated: 63 | return redirect("login") 64 | 65 | context = {} 66 | if request.POST: 67 | form = AccountUpdateForm(request.POST, instance=request.user) 68 | if form.is_valid(): 69 | form.initial = { 70 | "email": request.POST['email'], 71 | "username": request.POST['username'], 72 | } 73 | form.save() 74 | context['success_message'] = "Updated" 75 | else: 76 | form = AccountUpdateForm( 77 | 78 | initial={ 79 | "email": request.user.email, 80 | "username": request.user.username, 81 | } 82 | ) 83 | 84 | context['account_form'] = form 85 | 86 | blog_posts = BlogPost.objects.filter(author=request.user) 87 | context['blog_posts'] = blog_posts 88 | 89 | return render(request, "account/account.html", context) 90 | 91 | 92 | def must_authenticate_view(request): 93 | return render(request, 'account/must_authenticate.html', {}) 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__init__.py -------------------------------------------------------------------------------- /src/blog/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/admin.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/admin.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/forms.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/forms.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/models.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/models.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/serializers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/serializers.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/urls-api.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/urls-api.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/urls.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/urls.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/urls_api.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/urls_api.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/views.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/views.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/__pycache__/views_api.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/__pycache__/views_api.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from blog.models import BlogPost 4 | 5 | admin.site.register(BlogPost) 6 | -------------------------------------------------------------------------------- /src/blog/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/api/__init__.py -------------------------------------------------------------------------------- /src/blog/api/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/api/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/api/__pycache__/serializers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/api/__pycache__/serializers.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/api/__pycache__/urls.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/api/__pycache__/urls.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/api/__pycache__/views.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/api/__pycache__/views.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from blog.models import BlogPost 3 | 4 | import os 5 | from django.conf import settings 6 | from django.core.files.storage import default_storage 7 | from django.core.files.storage import FileSystemStorage 8 | IMAGE_SIZE_MAX_BYTES = 1024 * 1024 * 2 # 2MB 9 | MIN_TITLE_LENGTH = 5 10 | MIN_BODY_LENGTH = 50 11 | 12 | from blog.utils import is_image_aspect_ratio_valid, is_image_size_valid 13 | 14 | 15 | class BlogPostSerializer(serializers.ModelSerializer): 16 | 17 | username = serializers.SerializerMethodField('get_username_from_author') 18 | image = serializers.SerializerMethodField('validate_image_url') 19 | 20 | class Meta: 21 | model = BlogPost 22 | fields = ['pk', 'title', 'slug', 'body', 'image', 'date_updated', 'username'] 23 | 24 | 25 | def get_username_from_author(self, blog_post): 26 | username = blog_post.author.username 27 | return username 28 | 29 | def validate_image_url(self, blog_post): 30 | image = blog_post.image 31 | new_url = image.url 32 | if "?" in new_url: 33 | new_url = image.url[:image.url.rfind("?")] 34 | return new_url 35 | 36 | 37 | 38 | 39 | class BlogPostUpdateSerializer(serializers.ModelSerializer): 40 | 41 | class Meta: 42 | model = BlogPost 43 | fields = ['title', 'body', 'image'] 44 | 45 | def validate(self, blog_post): 46 | try: 47 | title = blog_post['title'] 48 | if len(title) < MIN_TITLE_LENGTH: 49 | raise serializers.ValidationError({"response": "Enter a title longer than " + str(MIN_TITLE_LENGTH) + " characters."}) 50 | 51 | body = blog_post['body'] 52 | if len(body) < MIN_BODY_LENGTH: 53 | raise serializers.ValidationError({"response": "Enter a body longer than " + str(MIN_BODY_LENGTH) + " characters."}) 54 | 55 | image = blog_post['image'] 56 | url = os.path.join(settings.TEMP , str(image)) 57 | storage = FileSystemStorage(location=url) 58 | 59 | with storage.open('', 'wb+') as destination: 60 | for chunk in image.chunks(): 61 | destination.write(chunk) 62 | destination.close() 63 | 64 | # Check image size 65 | if not is_image_size_valid(url, IMAGE_SIZE_MAX_BYTES): 66 | os.remove(url) 67 | raise serializers.ValidationError({"response": "That image is too large. Images must be less than 2 MB. Try a different image."}) 68 | 69 | # Check image aspect ratio 70 | if not is_image_aspect_ratio_valid(url): 71 | os.remove(url) 72 | raise serializers.ValidationError({"response": "Image height must not exceed image width. Try a different image."}) 73 | 74 | os.remove(url) 75 | except KeyError: 76 | pass 77 | return blog_post 78 | 79 | 80 | class BlogPostCreateSerializer(serializers.ModelSerializer): 81 | 82 | 83 | class Meta: 84 | model = BlogPost 85 | fields = ['title', 'body', 'image', 'date_updated', 'author'] 86 | 87 | 88 | def save(self): 89 | 90 | try: 91 | image = self.validated_data['image'] 92 | title = self.validated_data['title'] 93 | if len(title) < MIN_TITLE_LENGTH: 94 | raise serializers.ValidationError({"response": "Enter a title longer than " + str(MIN_TITLE_LENGTH) + " characters."}) 95 | 96 | body = self.validated_data['body'] 97 | if len(body) < MIN_BODY_LENGTH: 98 | raise serializers.ValidationError({"response": "Enter a body longer than " + str(MIN_BODY_LENGTH) + " characters."}) 99 | 100 | blog_post = BlogPost( 101 | author=self.validated_data['author'], 102 | title=title, 103 | body=body, 104 | image=image, 105 | ) 106 | 107 | url = os.path.join(settings.TEMP , str(image)) 108 | storage = FileSystemStorage(location=url) 109 | 110 | with storage.open('', 'wb+') as destination: 111 | for chunk in image.chunks(): 112 | destination.write(chunk) 113 | destination.close() 114 | 115 | # Check image size 116 | if not is_image_size_valid(url, IMAGE_SIZE_MAX_BYTES): 117 | os.remove(url) 118 | raise serializers.ValidationError({"response": "That image is too large. Images must be less than 2 MB. Try a different image."}) 119 | 120 | # Check image aspect ratio 121 | if not is_image_aspect_ratio_valid(url): 122 | os.remove(url) 123 | raise serializers.ValidationError({"response": "Image height must not exceed image width. Try a different image."}) 124 | 125 | os.remove(url) 126 | blog_post.save() 127 | return blog_post 128 | except KeyError: 129 | raise serializers.ValidationError({"response": "You must have a title, some content, and an image."}) 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/blog/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from blog.api.views import( 3 | api_detail_blog_view, 4 | api_update_blog_view, 5 | api_delete_blog_view, 6 | api_create_blog_view, 7 | api_is_author_of_blogpost, 8 | ApiBlogListView 9 | ) 10 | 11 | app_name = 'blog' 12 | 13 | urlpatterns = [ 14 | path('/', api_detail_blog_view, name="detail"), 15 | path('/update', api_update_blog_view, name="update"), 16 | path('/delete', api_delete_blog_view, name="delete"), 17 | path('create', api_create_blog_view, name="create"), 18 | path('list', ApiBlogListView.as_view(), name="list"), 19 | path('/is_author', api_is_author_of_blogpost, name="is_author"), 20 | ] -------------------------------------------------------------------------------- /src/blog/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.response import Response 3 | from rest_framework.decorators import api_view, permission_classes 4 | from rest_framework.permissions import IsAuthenticated 5 | from rest_framework.authentication import TokenAuthentication 6 | from rest_framework.pagination import PageNumberPagination 7 | from rest_framework.generics import ListAPIView 8 | from rest_framework.filters import SearchFilter, OrderingFilter 9 | 10 | from account.models import Account 11 | from blog.models import BlogPost 12 | from blog.api.serializers import BlogPostSerializer, BlogPostUpdateSerializer, BlogPostCreateSerializer 13 | 14 | SUCCESS = 'success' 15 | ERROR = 'error' 16 | DELETE_SUCCESS = 'deleted' 17 | UPDATE_SUCCESS = 'updated' 18 | CREATE_SUCCESS = 'created' 19 | 20 | # Response: https://gist.github.com/mitchtabian/93f287bd1370e7a1ad3c9588b0b22e3d 21 | # Url: https:///api/blog// 22 | # Headers: Authorization: Token 23 | @api_view(['GET', ]) 24 | @permission_classes((IsAuthenticated, )) 25 | def api_detail_blog_view(request, slug): 26 | 27 | try: 28 | blog_post = BlogPost.objects.get(slug=slug) 29 | except BlogPost.DoesNotExist: 30 | return Response(status=status.HTTP_404_NOT_FOUND) 31 | 32 | if request.method == 'GET': 33 | serializer = BlogPostSerializer(blog_post) 34 | return Response(serializer.data) 35 | 36 | 37 | # Response: https://gist.github.com/mitchtabian/32507e93c530aa5949bc08d795ba66df 38 | # Url: https:///api/blog//update 39 | # Headers: Authorization: Token 40 | @api_view(['PUT',]) 41 | @permission_classes((IsAuthenticated,)) 42 | def api_update_blog_view(request, slug): 43 | 44 | try: 45 | blog_post = BlogPost.objects.get(slug=slug) 46 | except BlogPost.DoesNotExist: 47 | return Response(status=status.HTTP_404_NOT_FOUND) 48 | 49 | user = request.user 50 | if blog_post.author != user: 51 | return Response({'response':"You don't have permission to edit that."}) 52 | 53 | if request.method == 'PUT': 54 | serializer = BlogPostUpdateSerializer(blog_post, data=request.data, partial=True) 55 | data = {} 56 | if serializer.is_valid(): 57 | serializer.save() 58 | data['response'] = UPDATE_SUCCESS 59 | data['pk'] = blog_post.pk 60 | data['title'] = blog_post.title 61 | data['body'] = blog_post.body 62 | data['slug'] = blog_post.slug 63 | data['date_updated'] = blog_post.date_updated 64 | image_url = str(request.build_absolute_uri(blog_post.image.url)) 65 | if "?" in image_url: 66 | image_url = image_url[:image_url.rfind("?")] 67 | data['image'] = image_url 68 | data['username'] = blog_post.author.username 69 | return Response(data=data) 70 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 71 | 72 | 73 | 74 | @api_view(['GET',]) 75 | @permission_classes((IsAuthenticated,)) 76 | def api_is_author_of_blogpost(request, slug): 77 | try: 78 | blog_post = BlogPost.objects.get(slug=slug) 79 | except BlogPost.DoesNotExist: 80 | return Response(status=status.HTTP_404_NOT_FOUND) 81 | 82 | data = {} 83 | user = request.user 84 | if blog_post.author != user: 85 | data['response'] = "You don't have permission to edit that." 86 | return Response(data=data) 87 | data['response'] = "You have permission to edit that." 88 | return Response(data=data) 89 | 90 | 91 | # Response: https://gist.github.com/mitchtabian/a97be3f8b71c75d588e23b414898ae5c 92 | # Url: https:///api/blog//delete 93 | # Headers: Authorization: Token 94 | @api_view(['DELETE',]) 95 | @permission_classes((IsAuthenticated, )) 96 | def api_delete_blog_view(request, slug): 97 | 98 | try: 99 | blog_post = BlogPost.objects.get(slug=slug) 100 | except BlogPost.DoesNotExist: 101 | return Response(status=status.HTTP_404_NOT_FOUND) 102 | 103 | user = request.user 104 | if blog_post.author != user: 105 | return Response({'response':"You don't have permission to delete that."}) 106 | 107 | if request.method == 'DELETE': 108 | operation = blog_post.delete() 109 | data = {} 110 | if operation: 111 | data['response'] = DELETE_SUCCESS 112 | return Response(data=data) 113 | 114 | 115 | # Response: https://gist.github.com/mitchtabian/78d7dcbeab4135c055ff6422238a31f9 116 | # Url: https:///api/blog/create 117 | # Headers: Authorization: Token 118 | @api_view(['POST']) 119 | @permission_classes((IsAuthenticated,)) 120 | def api_create_blog_view(request): 121 | 122 | if request.method == 'POST': 123 | 124 | data = request.data 125 | data['author'] = request.user.pk 126 | serializer = BlogPostCreateSerializer(data=data) 127 | 128 | data = {} 129 | if serializer.is_valid(): 130 | blog_post = serializer.save() 131 | data['response'] = CREATE_SUCCESS 132 | data['pk'] = blog_post.pk 133 | data['title'] = blog_post.title 134 | data['body'] = blog_post.body 135 | data['slug'] = blog_post.slug 136 | data['date_updated'] = blog_post.date_updated 137 | image_url = str(request.build_absolute_uri(blog_post.image.url)) 138 | if "?" in image_url: 139 | image_url = image_url[:image_url.rfind("?")] 140 | data['image'] = image_url 141 | data['username'] = blog_post.author.username 142 | return Response(data=data) 143 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 144 | 145 | 146 | # Response: https://gist.github.com/mitchtabian/ae03573737067c9269701ea662460205 147 | # Url: 148 | # 1) list: https:///api/blog/list 149 | # 2) pagination: http:///api/blog/list?page=2 150 | # 3) search: http:///api/blog/list?search=mitch 151 | # 4) ordering: http:///api/blog/list?ordering=-date_updated 152 | # 4) search + pagination + ordering: /api/blog/list?search=mitch&page=2&ordering=-date_updated 153 | # Headers: Authorization: Token 154 | class ApiBlogListView(ListAPIView): 155 | queryset = BlogPost.objects.all() 156 | serializer_class = BlogPostSerializer 157 | authentication_classes = (TokenAuthentication,) 158 | permission_classes = (IsAuthenticated,) 159 | pagination_class = PageNumberPagination 160 | filter_backends = (SearchFilter, OrderingFilter) 161 | search_fields = ('title', 'body', 'author__username') -------------------------------------------------------------------------------- /src/blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | -------------------------------------------------------------------------------- /src/blog/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from blog.models import BlogPost 4 | 5 | 6 | class CreateBlogPostForm(forms.ModelForm): 7 | 8 | class Meta: 9 | model = BlogPost 10 | fields = ['title', 'body', 'image'] 11 | 12 | 13 | 14 | class UpdateBlogPostForm(forms.ModelForm): 15 | 16 | class Meta: 17 | model = BlogPost 18 | fields = ['title', 'body', 'image'] 19 | 20 | def save(self, commit=True): 21 | blog_post = self.instance 22 | blog_post.title = self.cleaned_data['title'] 23 | blog_post.body = self.cleaned_data['body'] 24 | 25 | if self.cleaned_data['image']: 26 | blog_post.image = self.cleaned_data['image'] 27 | 28 | if commit: 29 | blog_post.save() 30 | return blog_post -------------------------------------------------------------------------------- /src/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-24 20:55 2 | 3 | import blog.models 4 | from django.conf import settings 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='BlogPost', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('title', models.CharField(max_length=50)), 23 | ('body', models.TextField(max_length=5000)), 24 | ('image', models.ImageField(upload_to=blog.models.upload_location)), 25 | ('date_published', models.DateTimeField(auto_now_add=True, verbose_name='date published')), 26 | ('date_updated', models.DateTimeField(auto_now=True, verbose_name='date updated')), 27 | ('slug', models.SlugField(blank=True, unique=True)), 28 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /src/blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/migrations/__init__.py -------------------------------------------------------------------------------- /src/blog/migrations/__pycache__/0001_initial.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/migrations/__pycache__/0001_initial.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/migrations/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/blog/migrations/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from django.utils.text import slugify 4 | from django.conf import settings 5 | from django.db.models.signals import post_delete, pre_save 6 | from django.dispatch import receiver 7 | 8 | 9 | def upload_location(instance, filename, **kwargs): 10 | file_path = 'blog/{author_id}/{title}-{filename}'.format( 11 | author_id=str(instance.author.id), title=str(instance.title), filename=filename 12 | ) 13 | return file_path 14 | 15 | 16 | class BlogPost(models.Model): 17 | title = models.CharField(max_length=50, null=False, blank=True) 18 | body = models.TextField(max_length=5000, null=False, blank=True) 19 | image = models.ImageField(upload_to=upload_location, null=False, blank=True) 20 | date_published = models.DateTimeField(auto_now_add=True, verbose_name="date published") 21 | date_updated = models.DateTimeField(auto_now=True, verbose_name="date updated") 22 | author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 23 | slug = models.SlugField(blank=True, unique=True) 24 | 25 | def __str__(self): 26 | return self.title 27 | 28 | @receiver(post_delete, sender=BlogPost) 29 | def submission_delete(sender, instance, **kwargs): 30 | instance.image.delete(False) 31 | 32 | def pre_save_blog_post_receiever(sender, instance, *args, **kwargs): 33 | if not instance.slug: 34 | instance.slug = slugify(instance.author.username + "-" + instance.title) 35 | 36 | pre_save.connect(pre_save_blog_post_receiever, sender=BlogPost) 37 | 38 | -------------------------------------------------------------------------------- /src/blog/templates/blog/create_blog.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 | 17 | 18 |
19 |
20 |
21 |
{% csrf_token %} 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | {% endblock content %} -------------------------------------------------------------------------------- /src/blog/templates/blog/detail_blog.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 |

{{blog_post.title}}

25 |

{{blog_post.body|linebreaksbr}}

26 |

27 | {% if blog_post.author == request.user %} 28 | Update 29 | {% endif %} 30 |

31 | 32 |
33 | 36 |
37 | 38 | 39 |
40 |
41 | 42 | {% endblock content %} -------------------------------------------------------------------------------- /src/blog/templates/blog/edit_blog.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 49 | 50 |
51 |
52 |
53 | 54 | {% if success_message %} 55 |

{{success_message}}

56 | {% endif %} 57 | 58 |
{% csrf_token %} 59 | 60 | 61 |
62 | 63 | 64 |
65 | 66 | 67 |
68 | 69 | 71 |
72 | 73 | 74 |
75 | 76 |
77 |
Change image
78 |
79 |
80 | 81 | 82 | 83 | {% for field in form %} 84 |

85 | {% for error in field.errors %} 86 |

{{ error }}

87 | {% endfor %} 88 |

89 | {% endfor %} 90 | {% if form.non_field_errors %} 91 |
92 |

{{form.non_field_errors}}

93 |
94 | {% endif %} 95 | 96 | 97 | 98 |
99 |
100 |
101 |
102 | 103 | 116 | 117 | {% endblock content %} -------------------------------------------------------------------------------- /src/blog/templates/blog/snippets/blog_post_pagination.html: -------------------------------------------------------------------------------- 1 | 2 | 30 | -------------------------------------------------------------------------------- /src/blog/templates/blog/snippets/blog_post_snippet.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | {% if blog_post %} 13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 |

{{blog_post.title}}

26 |
27 |

{{blog_post.body|linebreaksbr|truncatechars:250}}

28 | {% if blog_post.author == request.user %} 29 | Update 30 | {% endif %} 31 |
32 | 35 |
36 | 37 |
38 |
39 | 40 | {% else %} 41 | 42 |
43 |
44 |
45 |
46 |

47 | No results 48 |

49 |

There were no results matching your search: "{{query}}".

50 |
51 |
52 |
53 |
54 | {% endif %} -------------------------------------------------------------------------------- /src/blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from blog.views import( 3 | 4 | create_blog_view, 5 | detail_blog_view, 6 | edit_blog_view, 7 | 8 | ) 9 | 10 | app_name = 'blog' 11 | 12 | urlpatterns = [ 13 | path('create/', create_blog_view, name="create"), 14 | path('/', detail_blog_view, name="detail"), 15 | path('/edit', edit_blog_view, name="edit"), 16 | ] -------------------------------------------------------------------------------- /src/blog/utils.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | 4 | def is_image_aspect_ratio_valid(img_url): 5 | img = cv2.imread(img_url) 6 | dimensions = tuple(img.shape[1::-1]) # gives: (width, height) 7 | # print("dimensions: " + str(dimensions)) 8 | aspect_ratio = dimensions[0] / dimensions[1] # divide w / h 9 | # print("aspect_ratio: " + str(aspect_ratio)) 10 | if aspect_ratio < 1: 11 | return False 12 | return True 13 | 14 | 15 | def is_image_size_valid(img_url, mb_limit): 16 | image_size = os.path.getsize(img_url) 17 | # print("image size: " + str(image_size)) 18 | if image_size > mb_limit: 19 | return False 20 | return True -------------------------------------------------------------------------------- /src/blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.db.models import Q 3 | from django.http import HttpResponse 4 | 5 | from blog.models import BlogPost 6 | from blog.forms import CreateBlogPostForm, UpdateBlogPostForm 7 | 8 | from account.models import Account 9 | 10 | 11 | def create_blog_view(request): 12 | 13 | context = {} 14 | 15 | user = request.user 16 | if not user.is_authenticated: 17 | return redirect('must_authenticate') 18 | 19 | form = CreateBlogPostForm(request.POST or None, request.FILES or None) 20 | if form.is_valid(): 21 | obj = form.save(commit=False) 22 | author = Account.objects.filter(email=user.email).first() 23 | obj.author = author 24 | obj.save() 25 | form = CreateBlogPostForm() 26 | 27 | context['form'] = form 28 | 29 | return render(request, "blog/create_blog.html", context) 30 | 31 | 32 | def detail_blog_view(request, slug): 33 | 34 | context = {} 35 | 36 | blog_post = get_object_or_404(BlogPost, slug=slug) 37 | context['blog_post'] = blog_post 38 | 39 | return render(request, 'blog/detail_blog.html', context) 40 | 41 | 42 | 43 | def edit_blog_view(request, slug): 44 | 45 | context = {} 46 | 47 | user = request.user 48 | if not user.is_authenticated: 49 | return redirect("must_authenticate") 50 | 51 | blog_post = get_object_or_404(BlogPost, slug=slug) 52 | 53 | if blog_post.author != user: 54 | return HttpResponse("You are not the author of that post.") 55 | 56 | if request.POST: 57 | form = UpdateBlogPostForm(request.POST or None, request.FILES or None, instance=blog_post) 58 | if form.is_valid(): 59 | obj = form.save(commit=False) 60 | obj.save() 61 | context['success_message'] = "Updated" 62 | blog_post = obj 63 | 64 | form = UpdateBlogPostForm( 65 | initial = { 66 | "title": blog_post.title, 67 | "body": blog_post.body, 68 | "image": blog_post.image, 69 | } 70 | ) 71 | 72 | context['form'] = form 73 | return render(request, 'blog/edit_blog.html', context) 74 | 75 | 76 | def get_blog_queryset(query=None): 77 | queryset = [] 78 | queries = query.split(" ") # python install 2019 = [python, install, 2019] 79 | for q in queries: 80 | posts = BlogPost.objects.filter( 81 | Q(title__icontains=q) | 82 | Q(body__icontains=q) 83 | ).distinct() 84 | 85 | for post in posts: 86 | queryset.append(post) 87 | 88 | return list(set(queryset)) 89 | -------------------------------------------------------------------------------- /src/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/db.sqlite3 -------------------------------------------------------------------------------- /src/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', 'mysite.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 | -------------------------------------------------------------------------------- /src/media_cdn/blog/1/First_Blog_Post-Filtering_RecyclerView_with_SearchView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/media_cdn/blog/1/First_Blog_Post-Filtering_RecyclerView_with_SearchView.png -------------------------------------------------------------------------------- /src/media_cdn/blog/1/Second_Blog_post-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/media_cdn/blog/1/Second_Blog_post-4.png -------------------------------------------------------------------------------- /src/media_cdn/blog/1/Second_Blog_post-detecting_memory_leaks_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/media_cdn/blog/1/Second_Blog_post-detecting_memory_leaks_thumbnail.png -------------------------------------------------------------------------------- /src/media_cdn/blog/2/hdhfhgh-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/media_cdn/blog/2/hdhfhgh-11.png -------------------------------------------------------------------------------- /src/media_cdn/blog/3/gdfgdgdfg-Android_Thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/media_cdn/blog/3/gdfgdgdfg-Android_Thumbnail.png -------------------------------------------------------------------------------- /src/mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/mysite/__init__.py -------------------------------------------------------------------------------- /src/mysite/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/mysite/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/mysite/__pycache__/settings.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/mysite/__pycache__/settings.cpython-37.pyc -------------------------------------------------------------------------------- /src/mysite/__pycache__/urls.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/mysite/__pycache__/urls.cpython-37.pyc -------------------------------------------------------------------------------- /src/mysite/__pycache__/wsgi.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/mysite/__pycache__/wsgi.cpython-37.pyc -------------------------------------------------------------------------------- /src/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.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 | from decouple import config 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = '!p1ai_b373g3bmz-**m@%h9+0_8xm7*41etdbi+t266-mogm08' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | if DEBUG: 32 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # During development only 33 | 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 39 | # My apps 40 | 'personal', 41 | 'account', 42 | 'blog', 43 | 44 | 45 | # django apps 46 | 'django.contrib.admin', 47 | 'django.contrib.auth', 48 | 'django.contrib.contenttypes', 49 | 'django.contrib.sessions', 50 | 'django.contrib.messages', 51 | 'django.contrib.staticfiles', 52 | 'rest_framework', 53 | 'rest_framework.authtoken', 54 | ] 55 | 56 | AUTH_USER_MODEL = 'account.Account' 57 | AUTHENTICATION_BACKENDS = ( 58 | 'django.contrib.auth.backends.AllowAllUsersModelBackend', 59 | 'account.backends.CaseInsensitiveModelBackend', 60 | ) 61 | 62 | REST_FRAMEWORK = { 63 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 64 | 'rest_framework.authentication.TokenAuthentication', 65 | ), 66 | 'DEFAULT_PERMISSION_CLASSES': ( 67 | 'rest_framework.permissions.IsAuthenticated', 68 | ), 69 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 70 | 'PAGE_SIZE': 10, 71 | } 72 | 73 | MIDDLEWARE = [ 74 | 'django.middleware.security.SecurityMiddleware', 75 | 'django.contrib.sessions.middleware.SessionMiddleware', 76 | 'django.middleware.common.CommonMiddleware', 77 | 'django.middleware.csrf.CsrfViewMiddleware', 78 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 79 | 'django.contrib.messages.middleware.MessageMiddleware', 80 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 81 | ] 82 | 83 | ROOT_URLCONF = 'mysite.urls' 84 | 85 | TEMPLATES = [ 86 | { 87 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 88 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 89 | 'APP_DIRS': True, 90 | 'OPTIONS': { 91 | 'context_processors': [ 92 | 'django.template.context_processors.debug', 93 | 'django.template.context_processors.request', 94 | 'django.contrib.auth.context_processors.auth', 95 | 'django.contrib.messages.context_processors.messages', 96 | ], 97 | }, 98 | }, 99 | ] 100 | 101 | 102 | WSGI_APPLICATION = 'mysite.wsgi.application' 103 | 104 | 105 | # Database 106 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 107 | 108 | DATABASES = { 109 | 'default': { 110 | 'ENGINE': 'django.db.backends.sqlite3', 111 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 112 | } 113 | } 114 | 115 | 116 | # Password validation 117 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 118 | 119 | AUTH_PASSWORD_VALIDATORS = [ 120 | { 121 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 122 | }, 123 | { 124 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 125 | }, 126 | { 127 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 128 | }, 129 | { 130 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 131 | }, 132 | ] 133 | 134 | 135 | # Internationalization 136 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 137 | 138 | LANGUAGE_CODE = 'en-us' 139 | 140 | TIME_ZONE = 'UTC' 141 | 142 | USE_I18N = True 143 | 144 | USE_L10N = True 145 | 146 | USE_TZ = True 147 | 148 | 149 | # Static files (CSS, JavaScript, Images) 150 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 151 | STATICFILES_DIRS = [ 152 | os.path.join(BASE_DIR, 'static'), 153 | os.path.join(BASE_DIR, 'media'), 154 | ] 155 | STATIC_URL = '/static/' 156 | MEDIA_URL = '/media/' 157 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_cdn') 158 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media_cdn') 159 | TEMP = os.path.join(BASE_DIR, 'temp') -------------------------------------------------------------------------------- /src/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite 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.contrib import admin 17 | from django.urls import path, include 18 | from django.contrib.auth import views as auth_views 19 | from django.conf.urls.static import static 20 | from django.conf import settings 21 | 22 | from personal.views import ( 23 | home_screen_view, 24 | ) 25 | 26 | from account.views import ( 27 | registration_view, 28 | logout_view, 29 | login_view, 30 | account_view, 31 | must_authenticate_view, 32 | ) 33 | 34 | urlpatterns = [ 35 | path('', home_screen_view, name="home"), 36 | path('admin/', admin.site.urls), 37 | path('account/', account_view, name="account"), 38 | path('blog/', include('blog.urls', 'blog')), 39 | path('login/', login_view, name="login"), 40 | path('logout/', logout_view, name="logout"), 41 | path('must_authenticate/', must_authenticate_view, name="must_authenticate"), 42 | path('register/', registration_view, name="register"), 43 | 44 | # REST-framework 45 | path('api/blog/', include('blog.api.urls', 'blog_api')), 46 | path('api/account/', include('account.api.urls', 'account_api')), 47 | 48 | # Password reset links (ref: https://github.com/django/django/blob/master/django/contrib/auth/views.py) 49 | path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='registration/password_change_done.html'), 50 | name='password_change_done'), 51 | 52 | path('password_change/', auth_views.PasswordChangeView.as_view(template_name='registration/password_change.html'), 53 | name='password_change'), 54 | 55 | path('password_reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='registration/password_reset_done.html'), 56 | name='password_reset_done'), 57 | 58 | path('reset///', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), 59 | path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'), 60 | 61 | path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='registration/password_reset_complete.html'), 62 | name='password_reset_complete'), 63 | ] 64 | 65 | if settings.DEBUG: 66 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 67 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 68 | -------------------------------------------------------------------------------- /src/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite 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', 'mysite.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/personal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/__init__.py -------------------------------------------------------------------------------- /src/personal/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/__pycache__/admin.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/__pycache__/admin.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/__pycache__/models.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/__pycache__/models.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/__pycache__/views.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/__pycache__/views.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | -------------------------------------------------------------------------------- /src/personal/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PersonalConfig(AppConfig): 5 | name = 'personal' 6 | -------------------------------------------------------------------------------- /src/personal/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-19 18:01 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='Question', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=60)), 19 | ('question', models.TextField(max_length=400)), 20 | ('priority', models.CharField(choices=[('L', 'Low'), ('M', 'Medium'), ('H', 'High')], max_length=1)), 21 | ], 22 | options={ 23 | 'verbose_name': 'The Question', 24 | 'verbose_name_plural': 'Questions from People', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/personal/migrations/0002_delete_question.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-19 18:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('personal', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Question', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /src/personal/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__init__.py -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/0001_initial.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/0001_initial.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/0002_auto_20190617_0949.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/0002_auto_20190617_0949.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/0002_delete_question.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/0002_delete_question.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/0003_auto_20190617_0950.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/0003_auto_20190617_0950.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/0004_auto_20190617_0952.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/0004_auto_20190617_0952.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/migrations/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/personal/migrations/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /src/personal/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # #Two tuple structure 4 | # #The first element in each tuple is the value that will be stored in the database. The second element is displayed by the field’s form widget. 5 | # #Tuple 6 | # PRIORITY = [ 7 | # ('L', 'Low'), #Tuple1 8 | # ('M', 'Medium'), #Tuple2 9 | # ('H', 'High'), #Tuple3 10 | # ] 11 | 12 | # class Question(models.Model): 13 | # title = models.CharField(max_length=60) 14 | # question = models.TextField(max_length=400) 15 | # priority = models.CharField(max_length=1, choices=PRIORITY) 16 | 17 | # def __str__(self): 18 | # return self.title 19 | 20 | 21 | # class Meta: 22 | # verbose_name = 'The Question' 23 | # verbose_name_plural = 'Questions from People' -------------------------------------------------------------------------------- /src/personal/templates/personal/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 34 |
35 |
36 | 37 | 38 |
39 | 40 | 41 |
42 |
43 | 44 |

In this course you'll learn how to build a simple blog with user registration and blog CRUD functionality. Django is a powerful 45 | framework and you'll see why in this course.

46 |

Create post

47 |
48 |
49 | 50 | 51 | 52 | {% if blog_posts %} 53 | {% for post in blog_posts %} 54 |
55 | {% include 'blog/snippets/blog_post_snippet.html' with blog_post=post %} 56 |
57 | {% endfor %} 58 | {% else %} 59 |
60 | {% include 'blog/snippets/blog_post_snippet.html' with query=query %} 61 |
62 | {% endif %} 63 | 64 | 65 | 66 | {% include 'blog/snippets/blog_post_pagination.html' with blog_posts=blog_posts %} 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 |

In this course you'll learn how to build a simple blog with user registration and blog CRUD functionality. Django is a powerful 79 | framework and you'll see why in this course.

80 |

Create post

81 |
82 | 83 |
84 | 85 | 86 |
87 |
88 | 89 | {% endblock content %} -------------------------------------------------------------------------------- /src/personal/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/personal/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from operator import attrgetter 3 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 4 | 5 | from blog.views import get_blog_queryset 6 | from blog.models import BlogPost 7 | 8 | BLOG_POSTS_PER_PAGE = 10 9 | 10 | def home_screen_view(request): 11 | 12 | context = {} 13 | 14 | query = "" 15 | query = request.GET.get('q', '') 16 | context['query'] = str(query) 17 | print("home_screen_view: " + str(query)) 18 | 19 | blog_posts = sorted(get_blog_queryset(query), key=attrgetter('date_updated'), reverse=True) 20 | 21 | # Pagination 22 | page = request.GET.get('page', 1) 23 | blog_posts_paginator = Paginator(blog_posts, BLOG_POSTS_PER_PAGE) 24 | 25 | try: 26 | blog_posts = blog_posts_paginator.page(page) 27 | except PageNotAnInteger: 28 | blog_posts = blog_posts_paginator.page(BLOG_POSTS_PER_PAGE) 29 | except EmptyPage: 30 | blog_posts = blog_posts_paginator.page(blog_posts_paginator.num_pages) 31 | 32 | context['blog_posts'] = blog_posts 33 | 34 | return render(request, "personal/home.html", context) 35 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==2.5.0 2 | anyjson==0.3.3 3 | billiard==3.6.0.0 4 | celery==4.3.0 5 | certifi==2019.6.16 6 | chardet==3.0.4 7 | Django==2.2.2 8 | django-celery-beat==1.5.0 9 | django-celery-results==1.1.2 10 | django-timezone-field==3.0 11 | djangorestframework==3.9.4 12 | idna==2.8 13 | kombu==4.6.3 14 | numpy==1.16.4 15 | opencv-python==4.1.0.25 16 | Pillow==6.0.0 17 | python-crontab==2.3.8 18 | python-dateutil==2.8.0 19 | python-decouple==3.1 20 | pytz==2019.1 21 | redis==3.2.1 22 | requests==2.22.0 23 | six==1.12.0 24 | sqlparse==0.3.0 25 | urllib3==1.25.3 26 | vine==1.3.0 27 | virtualenv==16.6.0 28 | -------------------------------------------------------------------------------- /src/static/codingwithmitch_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/static/codingwithmitch_logo.png -------------------------------------------------------------------------------- /src/static_cdn/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | -o-text-overflow: ellipsis; 27 | } 28 | -------------------------------------------------------------------------------- /src/static_cdn/admin/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('../fonts/Roboto-Bold-webfont.woff'); 4 | font-weight: 700; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: url('../fonts/Roboto-Regular-webfont.woff'); 11 | font-weight: 400; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Roboto'; 17 | src: url('../fonts/Roboto-Light-webfont.woff'); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /src/static_cdn/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #f8f8f8; 5 | } 6 | 7 | .login #header { 8 | height: auto; 9 | padding: 15px 16px; 10 | justify-content: center; 11 | } 12 | 13 | .login #header h1 { 14 | font-size: 18px; 15 | } 16 | 17 | .login #header h1 a { 18 | color: #fff; 19 | } 20 | 21 | .login #content { 22 | padding: 20px 20px 0; 23 | } 24 | 25 | .login #container { 26 | background: #fff; 27 | border: 1px solid #eaeaea; 28 | border-radius: 4px; 29 | overflow: hidden; 30 | width: 28em; 31 | min-width: 300px; 32 | margin: 100px auto; 33 | } 34 | 35 | .login #content-main { 36 | width: 100%; 37 | } 38 | 39 | .login .form-row { 40 | padding: 4px 0; 41 | float: left; 42 | width: 100%; 43 | border-bottom: none; 44 | } 45 | 46 | .login .form-row label { 47 | padding-right: 0.5em; 48 | line-height: 2em; 49 | font-size: 1em; 50 | clear: both; 51 | color: #333; 52 | } 53 | 54 | .login .form-row #id_username, .login .form-row #id_password { 55 | clear: both; 56 | padding: 8px; 57 | width: 100%; 58 | -webkit-box-sizing: border-box; 59 | -moz-box-sizing: border-box; 60 | box-sizing: border-box; 61 | } 62 | 63 | .login span.help { 64 | font-size: 10px; 65 | display: block; 66 | } 67 | 68 | .login .submit-row { 69 | clear: both; 70 | padding: 1em 0 0 9.4em; 71 | margin: 0; 72 | border: none; 73 | background: none; 74 | text-align: left; 75 | } 76 | 77 | .login .password-reset-link { 78 | text-align: center; 79 | } 80 | -------------------------------------------------------------------------------- /src/static_cdn/admin/css/responsive_rtl.css: -------------------------------------------------------------------------------- 1 | /* TABLETS */ 2 | 3 | @media (max-width: 1024px) { 4 | [dir="rtl"] .colMS { 5 | margin-right: 0; 6 | } 7 | 8 | [dir="rtl"] #user-tools { 9 | text-align: right; 10 | } 11 | 12 | [dir="rtl"] #changelist .actions label { 13 | padding-left: 10px; 14 | padding-right: 0; 15 | } 16 | 17 | [dir="rtl"] #changelist .actions select { 18 | margin-left: 0; 19 | margin-right: 15px; 20 | } 21 | 22 | [dir="rtl"] .change-list .filtered .results, 23 | [dir="rtl"] .change-list .filtered .paginator, 24 | [dir="rtl"] .filtered #toolbar, 25 | [dir="rtl"] .filtered div.xfull, 26 | [dir="rtl"] .filtered .actions { 27 | margin-right: 0; 28 | margin-left: 230px; 29 | } 30 | 31 | [dir="rtl"] .inline-group ul.tools a.add, 32 | [dir="rtl"] .inline-group div.add-row a, 33 | [dir="rtl"] .inline-group .tabular tr.add-row td a { 34 | padding: 8px 26px 8px 10px; 35 | background-position: calc(100% - 8px) 9px; 36 | } 37 | 38 | [dir="rtl"] .related-widget-wrapper-link + .selector { 39 | margin-right: 0; 40 | margin-left: 15px; 41 | } 42 | 43 | [dir="rtl"] .selector .selector-filter label { 44 | margin-right: 0; 45 | margin-left: 8px; 46 | } 47 | 48 | [dir="rtl"] .object-tools li { 49 | float: right; 50 | } 51 | 52 | [dir="rtl"] .object-tools li + li { 53 | margin-left: 0; 54 | margin-right: 15px; 55 | } 56 | 57 | [dir="rtl"] .dashboard .module table td a { 58 | padding-left: 0; 59 | padding-right: 16px; 60 | } 61 | } 62 | 63 | /* MOBILE */ 64 | 65 | @media (max-width: 767px) { 66 | [dir="rtl"] .change-list .filtered .results, 67 | [dir="rtl"] .change-list .filtered .paginator, 68 | [dir="rtl"] .filtered #toolbar, 69 | [dir="rtl"] .filtered div.xfull, 70 | [dir="rtl"] .filtered .actions { 71 | margin-left: 0; 72 | } 73 | 74 | [dir="rtl"] .aligned .add-another, 75 | [dir="rtl"] .aligned .related-lookup, 76 | [dir="rtl"] .aligned .datetimeshortcuts { 77 | margin-left: 0; 78 | margin-right: 15px; 79 | } 80 | 81 | [dir="rtl"] .aligned ul { 82 | margin-right: 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/static_cdn/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .module ul, .module ol { 34 | margin-left: 0; 35 | margin-right: 1.5em; 36 | } 37 | 38 | .viewlink, .addlink, .changelink { 39 | padding-left: 0; 40 | padding-right: 16px; 41 | background-position: 100% 1px; 42 | } 43 | 44 | .deletelink { 45 | padding-left: 0; 46 | padding-right: 16px; 47 | background-position: 100% 1px; 48 | } 49 | 50 | .object-tools { 51 | float: left; 52 | } 53 | 54 | thead th:first-child, 55 | tfoot td:first-child { 56 | border-left: none; 57 | } 58 | 59 | /* LAYOUT */ 60 | 61 | #user-tools { 62 | right: auto; 63 | left: 0; 64 | text-align: left; 65 | } 66 | 67 | div.breadcrumbs { 68 | text-align: right; 69 | } 70 | 71 | #content-main { 72 | float: right; 73 | } 74 | 75 | #content-related { 76 | float: left; 77 | margin-left: -300px; 78 | margin-right: auto; 79 | } 80 | 81 | .colMS { 82 | margin-left: 300px; 83 | margin-right: 0; 84 | } 85 | 86 | /* SORTABLE TABLES */ 87 | 88 | table thead th.sorted .sortoptions { 89 | float: left; 90 | } 91 | 92 | thead th.sorted .text { 93 | padding-right: 0; 94 | padding-left: 42px; 95 | } 96 | 97 | /* dashboard styles */ 98 | 99 | .dashboard .module table td a { 100 | padding-left: .6em; 101 | padding-right: 16px; 102 | } 103 | 104 | /* changelists styles */ 105 | 106 | .change-list .filtered table { 107 | border-left: none; 108 | border-right: 0px none; 109 | } 110 | 111 | #changelist-filter { 112 | right: auto; 113 | left: 0; 114 | border-left: none; 115 | border-right: none; 116 | } 117 | 118 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 119 | margin-right: 0; 120 | margin-left: 280px; 121 | } 122 | 123 | #changelist-filter li.selected { 124 | border-left: none; 125 | padding-left: 10px; 126 | margin-left: 0; 127 | border-right: 5px solid #eaeaea; 128 | padding-right: 10px; 129 | margin-right: -15px; 130 | } 131 | 132 | .filtered .actions { 133 | margin-left: 280px; 134 | margin-right: 0; 135 | } 136 | 137 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 138 | border-right: none; 139 | border-left: none; 140 | } 141 | 142 | /* FORMS */ 143 | 144 | .aligned label { 145 | padding: 0 0 3px 1em; 146 | float: right; 147 | } 148 | 149 | .submit-row { 150 | text-align: left 151 | } 152 | 153 | .submit-row p.deletelink-box { 154 | float: right; 155 | } 156 | 157 | .submit-row input.default { 158 | margin-left: 0; 159 | } 160 | 161 | .vDateField, .vTimeField { 162 | margin-left: 2px; 163 | } 164 | 165 | .aligned .form-row input { 166 | margin-left: 5px; 167 | } 168 | 169 | form .aligned p.help, form .aligned div.help { 170 | clear: right; 171 | } 172 | 173 | form .aligned ul { 174 | margin-right: 163px; 175 | margin-left: 0; 176 | } 177 | 178 | form ul.inline li { 179 | float: right; 180 | padding-right: 0; 181 | padding-left: 7px; 182 | } 183 | 184 | input[type=submit].default, .submit-row input.default { 185 | float: left; 186 | } 187 | 188 | fieldset .fieldBox { 189 | float: right; 190 | margin-left: 20px; 191 | margin-right: 0; 192 | } 193 | 194 | .errorlist li { 195 | background-position: 100% 12px; 196 | padding: 0; 197 | } 198 | 199 | .errornote { 200 | background-position: 100% 12px; 201 | padding: 10px 12px; 202 | } 203 | 204 | /* WIDGETS */ 205 | 206 | .calendarnav-previous { 207 | top: 0; 208 | left: auto; 209 | right: 10px; 210 | } 211 | 212 | .calendarnav-next { 213 | top: 0; 214 | right: auto; 215 | left: 10px; 216 | } 217 | 218 | .calendar caption, .calendarbox h2 { 219 | text-align: center; 220 | } 221 | 222 | .selector { 223 | float: right; 224 | } 225 | 226 | .selector .selector-filter { 227 | text-align: right; 228 | } 229 | 230 | .inline-deletelink { 231 | float: left; 232 | } 233 | 234 | form .form-row p.datetime { 235 | overflow: hidden; 236 | } 237 | 238 | .related-widget-wrapper { 239 | float: right; 240 | } 241 | 242 | /* MISC */ 243 | 244 | .inline-related h2, .inline-group h2 { 245 | text-align: right 246 | } 247 | 248 | .inline-related h3 span.delete { 249 | padding-right: 20px; 250 | padding-left: inherit; 251 | left: 10px; 252 | right: inherit; 253 | float:left; 254 | } 255 | 256 | .inline-related h3 span.delete label { 257 | margin-left: inherit; 258 | margin-right: 2px; 259 | } 260 | 261 | /* IE7 specific bug fixes */ 262 | 263 | div.colM { 264 | position: relative; 265 | } 266 | 267 | .submit-row input { 268 | float: left; 269 | } 270 | -------------------------------------------------------------------------------- /src/static_cdn/admin/css/vendor/select2/LICENSE-SELECT2.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/static_cdn/admin/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Roboto webfont source: https://www.google.com/fonts/specimen/Roboto 2 | WOFF files extracted using https://github.com/majodev/google-webfonts-helper 3 | Weights used in this project: Light (300), Regular (400), Bold (700) 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/static_cdn/admin/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /src/static_cdn/admin/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/static_cdn/admin/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /src/static_cdn/admin/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/static_cdn/admin/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /src/static_cdn/admin/img/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Code Charm Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/README.txt: -------------------------------------------------------------------------------- 1 | All icons are taken from Font Awesome (http://fontawesome.io/) project. 2 | The Font Awesome font is licensed under the SIL OFL 1.1: 3 | - https://scripts.sil.org/OFL 4 | 5 | SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG 6 | Font-Awesome-SVG-PNG is licensed under the MIT license (see file license 7 | in current folder). 8 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/calendar-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/gis/move_vertex_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/gis/move_vertex_on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-changelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-unknown-alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-viewlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/icon-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/inline-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/selector-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/sorting-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/tooltag-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/img/tooltag-arrowright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | var SelectBox = { 4 | cache: {}, 5 | init: function(id) { 6 | var box = document.getElementById(id); 7 | var node; 8 | SelectBox.cache[id] = []; 9 | var cache = SelectBox.cache[id]; 10 | var boxOptions = box.options; 11 | var boxOptionsLength = boxOptions.length; 12 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 13 | node = boxOptions[i]; 14 | cache.push({value: node.value, text: node.text, displayed: 1}); 15 | } 16 | }, 17 | redisplay: function(id) { 18 | // Repopulate HTML select box from cache 19 | var box = document.getElementById(id); 20 | var node; 21 | $(box).empty(); // clear all options 22 | var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag 23 | var cache = SelectBox.cache[id]; 24 | for (var i = 0, j = cache.length; i < j; i++) { 25 | node = cache[i]; 26 | if (node.displayed) { 27 | var new_option = new Option(node.text, node.value, false, false); 28 | // Shows a tooltip when hovering over the option 29 | new_option.setAttribute("title", node.text); 30 | new_options += new_option.outerHTML; 31 | } 32 | } 33 | new_options += ''; 34 | box.outerHTML = new_options; 35 | }, 36 | filter: function(id, text) { 37 | // Redisplay the HTML select box, displaying only the choices containing ALL 38 | // the words in text. (It's an AND search.) 39 | var tokens = text.toLowerCase().split(/\s+/); 40 | var node, token; 41 | var cache = SelectBox.cache[id]; 42 | for (var i = 0, j = cache.length; i < j; i++) { 43 | node = cache[i]; 44 | node.displayed = 1; 45 | var node_text = node.text.toLowerCase(); 46 | var numTokens = tokens.length; 47 | for (var k = 0; k < numTokens; k++) { 48 | token = tokens[k]; 49 | if (node_text.indexOf(token) === -1) { 50 | node.displayed = 0; 51 | break; // Once the first token isn't found we're done 52 | } 53 | } 54 | } 55 | SelectBox.redisplay(id); 56 | }, 57 | delete_from_cache: function(id, value) { 58 | var node, delete_index = null; 59 | var cache = SelectBox.cache[id]; 60 | for (var i = 0, j = cache.length; i < j; i++) { 61 | node = cache[i]; 62 | if (node.value === value) { 63 | delete_index = i; 64 | break; 65 | } 66 | } 67 | cache.splice(delete_index, 1); 68 | }, 69 | add_to_cache: function(id, option) { 70 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 71 | }, 72 | cache_contains: function(id, value) { 73 | // Check if an item is contained in the cache 74 | var node; 75 | var cache = SelectBox.cache[id]; 76 | for (var i = 0, j = cache.length; i < j; i++) { 77 | node = cache[i]; 78 | if (node.value === value) { 79 | return true; 80 | } 81 | } 82 | return false; 83 | }, 84 | move: function(from, to) { 85 | var from_box = document.getElementById(from); 86 | var option; 87 | var boxOptions = from_box.options; 88 | var boxOptionsLength = boxOptions.length; 89 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 90 | option = boxOptions[i]; 91 | var option_value = option.value; 92 | if (option.selected && SelectBox.cache_contains(from, option_value)) { 93 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 94 | SelectBox.delete_from_cache(from, option_value); 95 | } 96 | } 97 | SelectBox.redisplay(from); 98 | SelectBox.redisplay(to); 99 | }, 100 | move_all: function(from, to) { 101 | var from_box = document.getElementById(from); 102 | var option; 103 | var boxOptions = from_box.options; 104 | var boxOptionsLength = boxOptions.length; 105 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 106 | option = boxOptions[i]; 107 | var option_value = option.value; 108 | if (SelectBox.cache_contains(from, option_value)) { 109 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 110 | SelectBox.delete_from_cache(from, option_value); 111 | } 112 | } 113 | SelectBox.redisplay(from); 114 | SelectBox.redisplay(to); 115 | }, 116 | sort: function(id) { 117 | SelectBox.cache[id].sort(function(a, b) { 118 | a = a.text.toLowerCase(); 119 | b = b.text.toLowerCase(); 120 | try { 121 | if (a > b) { 122 | return 1; 123 | } 124 | if (a < b) { 125 | return -1; 126 | } 127 | } 128 | catch (e) { 129 | // silently fail on IE 'unknown' exception 130 | } 131 | return 0; 132 | } ); 133 | }, 134 | select_all: function(id) { 135 | var box = document.getElementById(id); 136 | var boxOptions = box.options; 137 | var boxOptionsLength = boxOptions.length; 138 | for (var i = 0; i < boxOptionsLength; i++) { 139 | boxOptions[i].selected = 'selected'; 140 | } 141 | } 142 | }; 143 | window.SelectBox = SelectBox; 144 | })(django.jQuery); 145 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | /*global gettext, interpolate, ngettext*/ 2 | (function($) { 3 | 'use strict'; 4 | var lastChecked; 5 | 6 | $.fn.actions = function(opts) { 7 | var options = $.extend({}, $.fn.actions.defaults, opts); 8 | var actionCheckboxes = $(this); 9 | var list_editable_changed = false; 10 | var showQuestion = function() { 11 | $(options.acrossClears).hide(); 12 | $(options.acrossQuestions).show(); 13 | $(options.allContainer).hide(); 14 | }, 15 | showClear = function() { 16 | $(options.acrossClears).show(); 17 | $(options.acrossQuestions).hide(); 18 | $(options.actionContainer).toggleClass(options.selectedClass); 19 | $(options.allContainer).show(); 20 | $(options.counterContainer).hide(); 21 | }, 22 | reset = function() { 23 | $(options.acrossClears).hide(); 24 | $(options.acrossQuestions).hide(); 25 | $(options.allContainer).hide(); 26 | $(options.counterContainer).show(); 27 | }, 28 | clearAcross = function() { 29 | reset(); 30 | $(options.acrossInput).val(0); 31 | $(options.actionContainer).removeClass(options.selectedClass); 32 | }, 33 | checker = function(checked) { 34 | if (checked) { 35 | showQuestion(); 36 | } else { 37 | reset(); 38 | } 39 | $(actionCheckboxes).prop("checked", checked) 40 | .parent().parent().toggleClass(options.selectedClass, checked); 41 | }, 42 | updateCounter = function() { 43 | var sel = $(actionCheckboxes).filter(":checked").length; 44 | // data-actions-icnt is defined in the generated HTML 45 | // and contains the total amount of objects in the queryset 46 | var actions_icnt = $('.action-counter').data('actionsIcnt'); 47 | $(options.counterContainer).html(interpolate( 48 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 49 | sel: sel, 50 | cnt: actions_icnt 51 | }, true)); 52 | $(options.allToggle).prop("checked", function() { 53 | var value; 54 | if (sel === actionCheckboxes.length) { 55 | value = true; 56 | showQuestion(); 57 | } else { 58 | value = false; 59 | clearAcross(); 60 | } 61 | return value; 62 | }); 63 | }; 64 | // Show counter by default 65 | $(options.counterContainer).show(); 66 | // Check state of checkboxes and reinit state if needed 67 | $(this).filter(":checked").each(function(i) { 68 | $(this).parent().parent().toggleClass(options.selectedClass); 69 | updateCounter(); 70 | if ($(options.acrossInput).val() === 1) { 71 | showClear(); 72 | } 73 | }); 74 | $(options.allToggle).show().on('click', function() { 75 | checker($(this).prop("checked")); 76 | updateCounter(); 77 | }); 78 | $("a", options.acrossQuestions).on('click', function(event) { 79 | event.preventDefault(); 80 | $(options.acrossInput).val(1); 81 | showClear(); 82 | }); 83 | $("a", options.acrossClears).on('click', function(event) { 84 | event.preventDefault(); 85 | $(options.allToggle).prop("checked", false); 86 | clearAcross(); 87 | checker(0); 88 | updateCounter(); 89 | }); 90 | lastChecked = null; 91 | $(actionCheckboxes).on('click', function(event) { 92 | if (!event) { event = window.event; } 93 | var target = event.target ? event.target : event.srcElement; 94 | if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) { 95 | var inrange = false; 96 | $(lastChecked).prop("checked", target.checked) 97 | .parent().parent().toggleClass(options.selectedClass, target.checked); 98 | $(actionCheckboxes).each(function() { 99 | if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) { 100 | inrange = (inrange) ? false : true; 101 | } 102 | if (inrange) { 103 | $(this).prop("checked", target.checked) 104 | .parent().parent().toggleClass(options.selectedClass, target.checked); 105 | } 106 | }); 107 | } 108 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 109 | lastChecked = target; 110 | updateCounter(); 111 | }); 112 | $('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() { 113 | list_editable_changed = true; 114 | }); 115 | $('form#changelist-form button[name="index"]').on('click', function(event) { 116 | if (list_editable_changed) { 117 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 118 | } 119 | }); 120 | $('form#changelist-form input[name="_save"]').on('click', function(event) { 121 | var action_changed = false; 122 | $('select option:selected', options.actionContainer).each(function() { 123 | if ($(this).val()) { 124 | action_changed = true; 125 | } 126 | }); 127 | if (action_changed) { 128 | if (list_editable_changed) { 129 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 130 | } else { 131 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 132 | } 133 | } 134 | }); 135 | }; 136 | /* Setup plugin defaults */ 137 | $.fn.actions.defaults = { 138 | actionContainer: "div.actions", 139 | counterContainer: "span.action-counter", 140 | allContainer: "div.actions span.all", 141 | acrossInput: "div.actions input.select-across", 142 | acrossQuestions: "div.actions span.question", 143 | acrossClears: "div.actions span.clear", 144 | allToggle: "#action-toggle", 145 | selectedClass: "selected" 146 | }; 147 | $(document).ready(function() { 148 | var $actionsEls = $('tr input.action-select'); 149 | if ($actionsEls.length > 0) { 150 | $actionsEls.actions(); 151 | } 152 | }); 153 | })(django.jQuery); 154 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(e){var b=a.extend({},a.fn.actions.defaults,e),g=a(this),k=!1,l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},p=function(){n(); 2 | a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){if(c===g.length){var a=!0;l()}else a=!1,p();return a})};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().on("click",function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).on("click",function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).on("click",function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).on("click",function(c){c||(c=window.event);var d=c.target?c.target: 4 | c.srcElement;if(f&&a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").on("change","td:gt(0) :input",function(){k=!0}); 5 | a('form#changelist-form button[name="index"]').on("click",function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').on("click",function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")): 6 | confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){var e= 7 | a("tr input.action-select");0'+a.addText+""),l=d.find("tr:last a")):(c.filter(":last").after('"),l=c.filter(":last").next().find("a")));l.on("click",function(d){d.preventDefault();d=b("#"+a.prefix+"-empty"); 6 | var c=d.clone(!0);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+h);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){f(this,a.prefix,g.val())});c.insertBefore(b(d)); 7 | b(g).val(parseInt(g.val(),10)+1);h+=1;""!==e.val()&&0>=e.val()-g.val()&&l.parent().hide();c.find("a."+a.deleteCssClass).on("click",function(d){d.preventDefault();c.remove();--h;a.removed&&a.removed(c);b(document).trigger("formset:removed",[c,a.prefix]);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===e.val()||0 tr",b(d).tabularFormset(d,a.options)}})})})(django.jQuery); 14 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /*global django:true, jQuery:false*/ 2 | /* Puts the included jQuery into our own namespace using noConflict and passing 3 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 4 | * namespace (i.e. this preserves pre-existing values for both window.$ and 5 | * window.jQuery). 6 | */ 7 | var django = django || {}; 8 | django.jQuery = jQuery.noConflict(true); 9 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/popup_response.js: -------------------------------------------------------------------------------- 1 | /*global opener */ 2 | (function() { 3 | 'use strict'; 4 | var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse); 5 | switch(initData.action) { 6 | case 'change': 7 | opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value); 8 | break; 9 | case 'delete': 10 | opener.dismissDeleteRelatedObjectPopup(window, initData.value); 11 | break; 12 | default: 13 | opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj); 14 | break; 15 | } 16 | })(); 17 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | /*global URLify*/ 2 | (function($) { 3 | 'use strict'; 4 | $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { 5 | /* 6 | Depends on urlify.js 7 | Populates a selected field with the values of the dependent fields, 8 | URLifies and shortens the string. 9 | dependencies - array of dependent fields ids 10 | maxLength - maximum length of the URLify'd string 11 | allowUnicode - Unicode support of the URLify'd string 12 | */ 13 | return this.each(function() { 14 | var prepopulatedField = $(this); 15 | 16 | var populate = function() { 17 | // Bail if the field's value has been changed by the user 18 | if (prepopulatedField.data('_changed')) { 19 | return; 20 | } 21 | 22 | var values = []; 23 | $.each(dependencies, function(i, field) { 24 | field = $(field); 25 | if (field.val().length > 0) { 26 | values.push(field.val()); 27 | } 28 | }); 29 | prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); 30 | }; 31 | 32 | prepopulatedField.data('_changed', false); 33 | prepopulatedField.on('change', function() { 34 | prepopulatedField.data('_changed', true); 35 | }); 36 | 37 | if (!prepopulatedField.val()) { 38 | $(dependencies.join(',')).on('keyup change focus', populate); 39 | } 40 | }); 41 | }; 42 | })(django.jQuery); 43 | -------------------------------------------------------------------------------- /src/static_cdn/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.prepopulate=function(d,f,g){return this.each(function(){var a=b(this),h=function(){if(!a.data("_changed")){var e=[];b.each(d,function(a,c){c=b(c);01&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ca.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/cs.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím zadejte o jeden znak méně":n<=4?"Prosím zadejte o "+e(n,!0)+" znaky méně":"Prosím zadejte o "+n+" znaků méně"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím zadejte ještě jeden znak":n<=4?"Prosím zadejte ještě další "+e(n,!0)+" znaky":"Prosím zadejte ještě dalších "+n+" znaků"},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky":"Můžete zvolit maximálně "+n+" položek"},noResults:function(){return"Nenalezeny žádné položky"},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/da.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Angiv venligst "+t+" tegn mindre";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Angiv venligst "+t+" tegn mere";return n},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/de.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/el.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/en.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/es.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"La carga falló"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/et.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/eu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/fa.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/fi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/fr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/gl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Elimine ";return t===1?n+="un carácter":n+=t+" caracteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Engada ";return t===1?n+="un carácter":n+=t+" caracteres",n},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){var t="Só pode ";return e.maximum===1?t+="un elemento":t+=e.maximum+" elementos",t},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/he.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/hi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/hr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/hu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/id.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/is.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/it.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ja.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/km.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ko.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/lt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/lv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/mk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ms.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/nb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn ";return t>1?n+=" flere tegn":n+=" tegn til",n},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/nl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/pl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/pt-BR.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/pt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"carácter",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ro.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+"sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/ru.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/sk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/sr-Cyrl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/sr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/sv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/th.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/tr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/uk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/vi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+' ký tự"';return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/zh-CN.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/select2/i18n/zh-TW.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | (function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})(); -------------------------------------------------------------------------------- /src/static_cdn/admin/js/vendor/xregexp/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007-2012 Steven Levithan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/static_cdn/codingwithmitch_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchtabian/CodingWithMitchBlog-REST-API/cf31d957b9cf3aea843c801e12378970d091b31d/src/static_cdn/codingwithmitch_logo.png -------------------------------------------------------------------------------- /src/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% include 'snippets/base_css.html' %} 15 | {% include 'snippets/header.html' %} 16 | 17 | 18 | 24 |
    25 | {% block content %} 26 | 27 | {% endblock content %} 28 |
    29 | 30 | 31 | {% include 'snippets/footer.html' %} 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/templates/registration/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 33 | 34 | 35 | 49 | 50 | 51 | 52 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |
    6 |
    7 |

    Your password has been Updated.

    8 |
    9 |
    10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 |
    7 |
    8 |

    Your password has been reset. You may go ahead and sign in now.

    9 |
    10 |
    11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |
    6 | 7 |
    8 |

    9 | We've emailed you instructions for setting your password, if an account exists with the email you entered. 10 | You should receive them shortly. 11 |

    12 |

    13 | If you don't receive an email, please make sure you've entered the address you registered with, 14 | and check your spam folder. 15 |

    16 |

    Return to home page

    17 |
    18 |
    19 | 20 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | To initiate the password reset process for your Account {{ user.email }}, 3 | click the link below: 4 | 5 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} 6 | 7 | If clicking the link above doesn't work, please copy and paste the URL in a new browser 8 | window instead. 9 | 10 | Sincerely, 11 | The CodingWithMitch Team 12 | {% endautoescape %} -------------------------------------------------------------------------------- /src/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 | 35 | 36 | 37 | 42 | 43 | 81 | 82 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/registration/password_reset_subject.txt: -------------------------------------------------------------------------------- 1 | CodingWithMitch password reset -------------------------------------------------------------------------------- /src/templates/snippets/base_css.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/snippets/footer.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/templates/snippets/header.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 |
    21 |
    22 | {% if request.user.is_authenticated %} 23 |

    Hello, {{request.user.username}}

    24 | {% endif %} 25 |
    26 | 27 | 32 | 33 | 45 |
    46 | 47 | 50 | 58 | 59 | --------------------------------------------------------------------------------