├── .devcontainer ├── Dockerfile ├── build-assets │ ├── heroku_config.sh │ ├── http_server.py │ └── make_url.py ├── devcontainer.json └── docker-compose.yml ├── .github └── ISSUE_TEMPLATE │ └── user-story.md ├── .gitignore ├── .gitpod.yml ├── .vscode └── extensions.json ├── Procfile ├── README.md ├── comments ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── db.sqlite3 ├── drf_api ├── __init__.py ├── asgi.py ├── permissions.py ├── serializers.py ├── settings.py ├── urls.py ├── views.py └── wsgi.py ├── followers ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── frontend ├── .env.development.local ├── package-lock.json ├── package.json ├── public │ ├── favicon-16x16.png │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.js │ ├── App.module.css │ ├── App.test.js │ ├── api │ └── axiosDefaults.js │ ├── assets │ ├── logo.png │ ├── no-results.png │ └── upload.png │ ├── components │ ├── Asset.js │ ├── Avatar.js │ ├── MoreDropdown.js │ ├── NavBar.js │ ├── NotFound.js │ └── __tests__ │ │ └── NavBar.test.js │ ├── contexts │ ├── CurrentUserContext.js │ └── ProfileDataContext.js │ ├── hooks │ ├── useClickOutsideToggle.js │ └── useRedirect.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── mocks │ └── handlers.js │ ├── pages │ ├── auth │ │ ├── SignInForm.js │ │ └── SignUpForm.js │ ├── comments │ │ ├── Comment.js │ │ ├── CommentCreateForm.js │ │ └── CommentEditForm.js │ ├── posts │ │ ├── Post.js │ │ ├── PostCreateForm.js │ │ ├── PostEditForm.js │ │ ├── PostPage.js │ │ └── PostsPage.js │ └── profiles │ │ ├── PopularProfiles.js │ │ ├── Profile.js │ │ ├── ProfileEditForm.js │ │ ├── ProfilePage.js │ │ ├── UserPasswordForm.js │ │ └── UsernameForm.js │ ├── reportWebVitals.js │ ├── setupTests.js │ ├── styles │ ├── Asset.module.css │ ├── Avatar.module.css │ ├── Button.module.css │ ├── Comment.module.css │ ├── CommentCreateEditForm.module.css │ ├── MoreDropdown.module.css │ ├── NavBar.module.css │ ├── NotFound.module.css │ ├── Post.module.css │ ├── PostCreateEditForm.module.css │ ├── PostsPage.module.css │ ├── Profile.module.css │ ├── ProfilePage.module.css │ └── SignInUpForm.module.css │ └── utils │ └── utils.js ├── likes ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── package-lock.json ├── posts ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── profiles ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── runtime.txt └── staticfiles ├── admin ├── css │ ├── autocomplete.css │ ├── base.css │ ├── changelists.css │ ├── dashboard.css │ ├── fonts.css │ ├── forms.css │ ├── login.css │ ├── nav_sidebar.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 │ ├── admin │ ├── DateTimeShortcuts.js │ └── RelatedObjectLookups.js │ ├── autocomplete.js │ ├── calendar.js │ ├── cancel.js │ ├── change_form.js │ ├── collapse.js │ ├── core.js │ ├── inlines.js │ ├── jquery.init.js │ ├── nav_sidebar.js │ ├── popup_response.js │ ├── prepopulate.js │ ├── prepopulate_init.js │ ├── urlify.js │ └── vendor │ ├── jquery │ ├── LICENSE.txt │ ├── jquery.js │ └── jquery.min.js │ ├── select2 │ ├── LICENSE.md │ ├── i18n │ │ ├── af.js │ │ ├── ar.js │ │ ├── az.js │ │ ├── bg.js │ │ ├── bn.js │ │ ├── bs.js │ │ ├── ca.js │ │ ├── cs.js │ │ ├── da.js │ │ ├── de.js │ │ ├── dsb.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 │ │ ├── hsb.js │ │ ├── hu.js │ │ ├── hy.js │ │ ├── id.js │ │ ├── is.js │ │ ├── it.js │ │ ├── ja.js │ │ ├── ka.js │ │ ├── km.js │ │ ├── ko.js │ │ ├── lt.js │ │ ├── lv.js │ │ ├── mk.js │ │ ├── ms.js │ │ ├── nb.js │ │ ├── ne.js │ │ ├── nl.js │ │ ├── pl.js │ │ ├── ps.js │ │ ├── pt-BR.js │ │ ├── pt.js │ │ ├── ro.js │ │ ├── ru.js │ │ ├── sk.js │ │ ├── sl.js │ │ ├── sq.js │ │ ├── sr-Cyrl.js │ │ ├── sr.js │ │ ├── sv.js │ │ ├── th.js │ │ ├── tk.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 ├── build ├── asset-manifest.json ├── favicon-16x16.png ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt └── static │ ├── css │ ├── main.ecc2cd5e.css │ └── main.ecc2cd5e.css.map │ └── js │ ├── 787.5ad96c4b.chunk.js │ ├── 787.5ad96c4b.chunk.js.map │ ├── main.3ff53e20.js │ ├── main.3ff53e20.js.LICENSE.txt │ └── main.3ff53e20.js.map ├── cloudinary ├── html │ └── cloudinary_cors.html └── js │ ├── canvas-to-blob.min.js │ ├── jquery.cloudinary.js │ ├── jquery.fileupload-image.js │ ├── jquery.fileupload-process.js │ ├── jquery.fileupload-validate.js │ ├── jquery.fileupload.js │ ├── jquery.iframe-transport.js │ ├── jquery.ui.widget.js │ └── load-image.all.min.js └── rest_framework ├── css ├── bootstrap-theme.min.css ├── bootstrap-theme.min.css.map ├── bootstrap-tweaks.css ├── bootstrap.min.css ├── bootstrap.min.css.map ├── default.css ├── font-awesome-4.0.3.css └── prettify.css ├── docs ├── css │ ├── base.css │ ├── highlight.css │ └── jquery.json-view.min.css ├── img │ ├── favicon.ico │ └── grid.png └── js │ ├── api.js │ ├── highlight.pack.js │ └── jquery.json-view.min.js ├── fonts ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── img ├── glyphicons-halflings-white.png ├── glyphicons-halflings.png └── grid.png └── js ├── ajax-form.js ├── bootstrap.min.js ├── coreapi-0.1.1.js ├── csrf.js ├── default.js ├── jquery-3.5.1.min.js └── prettify-min.js /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV PYTHON_VERSION 3.8.12 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | #Set of all dependencies needed for pyenv to work on Ubuntu 6 | RUN apt-get update \ 7 | && apt-get install -y --no-install-recommends make build-essential libssl-dev libpq-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget ca-certificates curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev mecab-ipadic-utf8 git postgresql-client telnet unzip zlib1g-dev 8 | 9 | RUN curl https://cli-assets.heroku.com/install.sh | sh 10 | RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs 11 | 12 | ARG USERNAME=codeany 13 | RUN useradd -ms /bin/bash $USERNAME 14 | USER $USERNAME 15 | WORKDIR /home/$USERNAME 16 | 17 | # Set-up necessary Env vars for PyEnv 18 | ENV PYENV_ROOT /home/$USERNAME/.pyenv 19 | ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH 20 | 21 | # Install pyenv 22 | RUN set -ex \ 23 | && curl https://pyenv.run | bash \ 24 | && pyenv update \ 25 | && pyenv install $PYTHON_VERSION \ 26 | && pyenv global $PYTHON_VERSION \ 27 | && pyenv rehash \ 28 | && python3 -m pip install --no-cache-dir --upgrade pip \ 29 | && python3 -m pip install --no-cache-dir --upgrade setuptools wheel virtualenv pipenv pylint rope flake8 mypy autopep8 pep8 pylama pydocstyle bandit notebook twine 30 | 31 | ENV PATH=$PATH:"/home/codeany/.local/bin" 32 | 33 | RUN echo 'alias python=python3' >> ~/.bashrc && \ 34 | echo 'export PIP_USER=yes' >> ~/.bashrc && \ 35 | echo 'alias pip=pip3' >> ~/.bashrc && \ 36 | echo 'alias psql="psql mydb"' >> ~/.bashrc 37 | 38 | COPY ./build-assets/heroku_config.sh /home/$USERNAME/.theia/heroku_config.sh 39 | RUN echo 'alias heroku_config=". $HOME/.theia/heroku_config.sh"' >> ~/.bashrc 40 | 41 | COPY ./build-assets/make_url.py /home/$USERNAME/.theia/make_url.py 42 | RUN echo 'alias make_url="python3 $HOME/.theia/make_url.py "' >> ~/.bashrc 43 | 44 | COPY ./build-assets/http_server.py /home/$USERNAME/.theia/http_server.py 45 | RUN echo 'alias http_server="python3 $HOME/.theia/http_server.py "' >> ~/.bashrc 46 | 47 | 48 | USER root 49 | RUN chown -R $USERNAME:$USERNAME /home/$USERNAME/.theia 50 | 51 | CMD ["tail", "-f", "/dev/null"] 52 | 53 | # Allows proxy to work for react/drf on cloud ide's 54 | ENV DANGEROUSLY_DISABLE_HOST_CHECK=true 55 | -------------------------------------------------------------------------------- /.devcontainer/build-assets/heroku_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script to allow Heroku API key to be pasted 3 | # exported as an environment variable 4 | # 5 | # Matt Rudge, May 2021 6 | 7 | echo Heroku authentication configuration script 8 | echo Code Institute, 2021 9 | echo 10 | echo Get your Heroku API key by going to https://dashboard.heroku.com 11 | echo Go to Account Settings and click on Reveal to view your Heroku API key 12 | echo 13 | 14 | if [[ -z "${HEROKU_API_KEY}" ]]; then 15 | echo Paste your Heroku API key here or press Enter to quit: 16 | read apikey 17 | if [[ -z "${apikey}" ]]; then 18 | return 0 19 | fi 20 | echo export HEROKU_API_KEY=${apikey} >> ~/.bashrc 21 | echo Added the export. Refreshing the terminal. 22 | . ~/.bashrc > /dev/null 23 | echo Done! 24 | else 25 | echo API key is already set. 26 | echo 27 | echo To reset the API key please input "'reset'": 28 | read reset_trigger 29 | if [[ ${reset_trigger} == reset ]]; then 30 | unset HEROKU_API_KEY 31 | unset reset_trigger 32 | echo 33 | echo API key removed! 34 | else 35 | unset reset_trigger 36 | echo API key unchanged. 37 | fi 38 | echo 39 | echo Exiting 40 | fi -------------------------------------------------------------------------------- /.devcontainer/build-assets/http_server.py: -------------------------------------------------------------------------------- 1 | # Simple wrapper for http.server 2 | # to switch off caching for users 3 | # 4 | # Matt Rudge 5 | # 20th April, 2023 6 | 7 | import http.server 8 | 9 | 10 | class NoCacheHTTPHandler(http.server.SimpleHTTPRequestHandler): 11 | def end_headers(self): 12 | """ 13 | Overrides default end_headers method 14 | """ 15 | self.send_cache_headers() 16 | http.server.SimpleHTTPRequestHandler.end_headers(self) 17 | 18 | def send_cache_headers(self): 19 | """ 20 | New method to send cache control headers 21 | """ 22 | self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") 23 | self.send_header("Pragma", "no-cache") 24 | self.send_header("Expires", "0") 25 | 26 | 27 | if __name__ == '__main__': 28 | http.server.test(HandlerClass=NoCacheHTTPHandler) 29 | -------------------------------------------------------------------------------- /.devcontainer/build-assets/make_url.py: -------------------------------------------------------------------------------- 1 | # Simple utility for creating the Cloudinary URL from a 2 | # cloudinary_python.txt file 3 | # Matt Rudge, November 2021 4 | 5 | import re 6 | 7 | with open("cloudinary_python.txt") as f: 8 | content = f.readlines() 9 | 10 | cloud_name = re.findall(r"['](.*?)[']",content[15])[0] 11 | api_key = re.findall(r"['](.*?)[']",content[16])[0] 12 | api_secret = re.findall(r"['](.*?)[']",content[17])[0] 13 | 14 | print(f"cloudinary://{api_key}:{api_secret}@{cloud_name}") -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - "/tmp/postgres/socket:/var/run/postgresql" 9 | 10 | psql: 11 | image: postgres:11.9 12 | environment: 13 | - POSTGRES_DB=mydb 14 | - POSTGRES_USER=codeany 15 | - POSTGRES_PASSWORD=pass1234 16 | network_mode: service:app 17 | volumes: 18 | - "/workspace/db-data:/var/lib/postgresql/data" 19 | - "/tmp/postgres/socket:/var/run/postgresql" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User Story 3 | about: Default User Story template 4 | title: 'User Story: ' 5 | labels: user story 6 | assignees: '' 7 | 8 | --- 9 | 10 | Epic: <epic> 11 | 12 | As a **role** I can **capability** so that **received benefit** 13 | 14 | ### Acceptance Criteria 15 | 16 | - Acceptance criteria 1 17 | - Acceptance criteria 2 18 | - Acceptance criteria 3 19 | 20 | ### Tasks 21 | 22 | - Task 1 23 | - Task 2 24 | - Task 3 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | core.mongo* 2 | core.python* 3 | env.py 4 | __pycache__/ 5 | *.py[cod] 6 | node_modules/ 7 | .github/ 8 | cloudinary_python.txt -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: pip install -r requirements.txt 9 | 10 | 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.python", 4 | "ms-python.isort", 5 | "eventyret.bootstrap-4-cdn-snippet", 6 | "dsznajder.es7-react-js-snippets", 7 | "esbenp.prettier-vscode", 8 | "formulahendry.auto-close-tag" 9 | ] 10 | } -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn drf_api.wsgi -------------------------------------------------------------------------------- /comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/comments/__init__.py -------------------------------------------------------------------------------- /comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'comments' 7 | -------------------------------------------------------------------------------- /comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2024-01-28 17:04 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('posts', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Comment', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('made_at', models.DateTimeField(auto_now_add=True)), 23 | ('edited_at', models.DateTimeField(auto_now=True)), 24 | ('content', models.TextField()), 25 | ('account_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='posts.post')), 27 | ], 28 | options={ 29 | 'ordering': ['-made_at'], 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/comments/migrations/__init__.py -------------------------------------------------------------------------------- /comments/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from posts.models import Post 4 | 5 | 6 | class Comment(models.Model): 7 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 8 | content = models.TextField() 9 | account_owner = models.ForeignKey(User, on_delete=models.CASCADE) 10 | made_at = models.DateTimeField(auto_now_add=True) 11 | edited_at = models.DateTimeField(auto_now=True) 12 | 13 | class Meta: 14 | ordering = ['-made_at'] 15 | 16 | def __str__(self): 17 | return self.content -------------------------------------------------------------------------------- /comments/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.humanize.templatetags.humanize import naturaltime 2 | from rest_framework import serializers 3 | from .models import Comment 4 | 5 | 6 | class CommentSerializer(serializers.ModelSerializer): 7 | account_owner = serializers.ReadOnlyField(source='account_owner.username') 8 | is_account_owner = serializers.SerializerMethodField() 9 | profile_id = serializers.ReadOnlyField(source='account_owner.profile.id') 10 | profile_image = serializers.ReadOnlyField(source='account_owner.profile.image.url') 11 | made_at = serializers.SerializerMethodField() 12 | edited_at = serializers.SerializerMethodField() 13 | 14 | def get_is_account_owner(self, obj): 15 | request = self.context['request'] 16 | return request.user == obj.account_owner 17 | 18 | def get_made_at(self, obj): 19 | return naturaltime(obj.made_at) 20 | 21 | def get_edited_at(self, obj): 22 | return naturaltime(obj.edited_at) 23 | 24 | class Meta: 25 | model = Comment 26 | fields = [ 27 | 'id', 'account_owner', 'is_account_owner', 'profile_id', 'profile_image', 28 | 'post', 'made_at', 'edited_at', 'content' 29 | ] 30 | 31 | 32 | class CommentDetailSerializer(CommentSerializer): 33 | post = serializers.ReadOnlyField(source='post.id') -------------------------------------------------------------------------------- /comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /comments/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from comments import views 3 | 4 | urlpatterns = [ 5 | path('comments/', views.CommentList.as_view()), 6 | path('comments/<int:pk>/', views.CommentDetail.as_view()) 7 | ] -------------------------------------------------------------------------------- /comments/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, permissions 2 | from django_filters.rest_framework import DjangoFilterBackend 3 | from drf_api.permissions import IsAccountOwnerOrReadOnly 4 | from .models import Comment 5 | from .serializers import CommentSerializer, CommentDetailSerializer 6 | 7 | 8 | class CommentList(generics.ListCreateAPIView): 9 | serializer_class = CommentSerializer 10 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 11 | queryset = Comment.objects.all() 12 | filter_backends = [DjangoFilterBackend] 13 | filterset_fields = ['post'] 14 | 15 | def perform_create(self, serializer): 16 | serializer.save(account_owner=self.request.user) 17 | 18 | 19 | class CommentDetail(generics.RetrieveUpdateDestroyAPIView): 20 | permission_classes = [IsAccountOwnerOrReadOnly] 21 | serializer_class = CommentDetailSerializer 22 | queryset = Comment.objects.all() -------------------------------------------------------------------------------- /drf_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/drf_api/__init__.py -------------------------------------------------------------------------------- /drf_api/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for drf_api project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_api.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /drf_api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsAccountOwnerOrReadOnly(permissions.BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | if request.method in permissions.SAFE_METHODS: 7 | return True 8 | return obj.account_owner == request.user 9 | -------------------------------------------------------------------------------- /drf_api/serializers.py: -------------------------------------------------------------------------------- 1 | from dj_rest_auth.serializers import UserDetailsSerializer 2 | from rest_framework import serializers 3 | 4 | 5 | class CurrentUserSerializer(UserDetailsSerializer): 6 | profile_id = serializers.ReadOnlyField(source='profile.id') 7 | profile_image = serializers.ReadOnlyField(source='profile.image.url') 8 | 9 | class Meta(UserDetailsSerializer.Meta): 10 | fields = UserDetailsSerializer.Meta.fields + ( 11 | 'profile_id', 'profile_image' 12 | ) -------------------------------------------------------------------------------- /drf_api/urls.py: -------------------------------------------------------------------------------- 1 | """drf_api URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.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.views.generic import TemplateView 19 | from .views import logout_route 20 | 21 | urlpatterns = [ 22 | path('', TemplateView.as_view(template_name='index.html')), 23 | path('admin/', admin.site.urls), 24 | path('api/api-auth/', include('rest_framework.urls')), 25 | path('api/dj-rest-auth/logout/', logout_route), 26 | path('api/dj-rest-auth/', include('dj_rest_auth.urls')), 27 | path('api/dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')), 28 | path('api/', include('profiles.urls')), 29 | path('api/', include('posts.urls')), 30 | path('api/', include('comments.urls')), 31 | path('api/', include('likes.urls')), 32 | path('api/', include('followers.urls')), 33 | ] 34 | 35 | handler404 = TemplateView.as_view(template_name='index.html') 36 | -------------------------------------------------------------------------------- /drf_api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.decorators import api_view 2 | from rest_framework.response import Response 3 | from .settings import ( 4 | JWT_AUTH_COOKIE, JWT_AUTH_REFRESH_COOKIE, JWT_AUTH_SAMESITE, 5 | JWT_AUTH_SECURE, 6 | ) 7 | 8 | 9 | @api_view() 10 | def root_route(request): 11 | return Response({ 12 | "message": "'Capture The Moments' drf API" 13 | }) 14 | 15 | # dj-rest-auth logout view fix 16 | @api_view(['POST']) 17 | def logout_route(request): 18 | response = Response() 19 | response.set_cookie( 20 | key=JWT_AUTH_COOKIE, 21 | value='', 22 | httponly=True, 23 | expires='Thu, 01 Jan 1970 00:00:00 GMT', 24 | max_age=0, 25 | samesite=JWT_AUTH_SAMESITE, 26 | secure=JWT_AUTH_SECURE, 27 | ) 28 | response.set_cookie( 29 | key=JWT_AUTH_REFRESH_COOKIE, 30 | value='', 31 | httponly=True, 32 | expires='Thu, 01 Jan 1970 00:00:00 GMT', 33 | max_age=0, 34 | samesite=JWT_AUTH_SAMESITE, 35 | secure=JWT_AUTH_SECURE, 36 | ) 37 | return response -------------------------------------------------------------------------------- /drf_api/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for drf_api project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.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', 'drf_api.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /followers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/followers/__init__.py -------------------------------------------------------------------------------- /followers/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /followers/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FollowersConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'followers' 7 | -------------------------------------------------------------------------------- /followers/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2024-01-28 17:04 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Follower', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('made_at', models.DateTimeField(auto_now_add=True)), 22 | ('account_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='following', to=settings.AUTH_USER_MODEL)), 23 | ('followed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='followed', to=settings.AUTH_USER_MODEL)), 24 | ], 25 | options={ 26 | 'ordering': ['-made_at'], 27 | 'unique_together': {('account_owner', 'followed')}, 28 | }, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /followers/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/followers/migrations/__init__.py -------------------------------------------------------------------------------- /followers/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Follower(models.Model): 6 | followed = models.ForeignKey(User, related_name='followed', on_delete=models.CASCADE) 7 | account_owner = models.ForeignKey(User, related_name='following', on_delete=models.CASCADE) 8 | made_at = models.DateTimeField(auto_now_add=True) 9 | 10 | class Meta: 11 | ordering = ['-made_at'] 12 | unique_together = ['account_owner', 'followed'] 13 | 14 | def __str__(self): 15 | return f'{self.account_owner} {self.followed}' -------------------------------------------------------------------------------- /followers/serializers.py: -------------------------------------------------------------------------------- 1 | from django.db import IntegrityError 2 | from rest_framework import serializers 3 | from .models import Follower 4 | 5 | 6 | class FollowerSerializer(serializers.ModelSerializer): 7 | account_owner = serializers.ReadOnlyField(source='account_owner.username') 8 | followed_name = serializers.ReadOnlyField(source='followed.username') 9 | 10 | class Meta: 11 | model = Follower 12 | fields = [ 13 | 'id', 'account_owner', 'made_at', 'followed', 'followed_name' 14 | ] 15 | 16 | def create(self, validated_data): 17 | try: 18 | return super().create(validated_data) 19 | except IntegrityError: 20 | raise serializers.ValidationError({'detail': 'possible duplicate'}) -------------------------------------------------------------------------------- /followers/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /followers/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from followers import views 3 | 4 | urlpatterns = [ 5 | path('followers/', views.FollowerList.as_view()), 6 | path('followers/<int:pk>/', views.FollowerDetail.as_view()) 7 | ] -------------------------------------------------------------------------------- /followers/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, permissions 2 | from drf_api.permissions import IsAccountOwnerOrReadOnly 3 | from .models import Follower 4 | from .serializers import FollowerSerializer 5 | 6 | 7 | class FollowerList(generics.ListCreateAPIView): 8 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 9 | queryset = Follower.objects.all() 10 | serializer_class = FollowerSerializer 11 | 12 | def perform_create(self, serializer): 13 | serializer.save(account_owner=self.request.user) 14 | 15 | 16 | class FollowerDetail(generics.RetrieveDestroyAPIView): 17 | permission_classes = [IsAccountOwnerOrReadOnly] 18 | queryset = Follower.objects.all() 19 | serializer_class = FollowerSerializer -------------------------------------------------------------------------------- /frontend/.env.development.local: -------------------------------------------------------------------------------- 1 | DANGEROUSLY_DISABLE_HOST_CHECK=true -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^1.6.7", 10 | "bootstrap": "^4.6.0", 11 | "jwt-decode": "^3.1.2", 12 | "react": "^17.0.2", 13 | "react-bootstrap": "^1.6.3", 14 | "react-dom": "^17.0.2", 15 | "react-infinite-scroll-component": "^6.1.0", 16 | "react-router-dom": "^5.3.0", 17 | "react-scripts": "^5.0.1", 18 | "web-vitals": "^1.1.2" 19 | }, 20 | "scripts": { 21 | "heroku-prebuild": "npm install -g serve", 22 | "start": "react-scripts --openssl-legacy-provider start", 23 | "build": "react-scripts --openssl-legacy-provider build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@types/react-router-dom": "^5.3.3", 47 | "msw": "^0.35.0", 48 | "react-app-rewired": "^2.2.1" 49 | }, 50 | "engines": { 51 | "node": "16.19.1", 52 | "npm": "8.19.3" 53 | }, 54 | "proxy": "http://localhost:8000/" 55 | } 56 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <link rel="icon" href="%PUBLIC_URL%/favicon-16x16.png" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 | <meta name="theme-color" content="#000000" /> 8 | <meta 9 | name="description" 10 | content="Web site created using create-react-app" 11 | /> 12 | <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> 13 | <!-- 14 | manifest.json provides metadata used when your web app is installed on a 15 | user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 16 | --> 17 | <link 18 | rel="stylesheet" 19 | href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" 20 | integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" 21 | crossorigin="anonymous" 22 | /> 23 | <link 24 | rel="manifest" 25 | href="%PUBLIC_URL%/manifest.json" 26 | crossorigin="use-credentials" 27 | /> 28 | <link 29 | href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&display=swap" 30 | rel="stylesheet" 31 | /> 32 | <script 33 | src="https://kit.fontawesome.com/6999690225.js" 34 | crossorigin="anonymous" 35 | ></script> 36 | <!-- 37 | Notice the use of %PUBLIC_URL% in the tags above. 38 | It will be replaced with the URL of the `public` folder during the build. 39 | Only files inside the `public` folder can be referenced from the HTML. 40 | 41 | Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will 42 | work correctly both with client-side routing and a non-root public URL. 43 | Learn how to configure a non-root public URL by running `npm run build`. 44 | --> 45 | <title>Capture The Moment 46 | 47 | 48 | 49 |
50 | 60 | 61 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import styles from "./App.module.css"; 2 | import NavBar from "./components/NavBar"; 3 | import Container from "react-bootstrap/Container"; 4 | import { Route, Switch } from "react-router-dom"; 5 | import './api/axiosDefaults'; 6 | import SignUpForm from "./pages/auth/SignUpForm"; 7 | import SignInForm from "./pages/auth/SignInForm"; 8 | import PostCreateForm from "./pages/posts/PostCreateForm"; 9 | import PostPage from "./pages/posts/PostPage"; 10 | import PostsPage from "./pages/posts/PostsPage"; 11 | import { useCurrentUser } from "./contexts/CurrentUserContext"; 12 | import PostEditForm from "./pages/posts/PostEditForm"; 13 | import ProfilePage from "./pages/profiles/ProfilePage"; 14 | import UsernameForm from "./pages/profiles/UsernameForm"; 15 | import UserPasswordForm from "./pages/profiles/UserPasswordForm"; 16 | import ProfileEditForm from "./pages/profiles/ProfileEditForm"; 17 | import NotFound from "./components/NotFound"; 18 | 19 | function App() { 20 | const currentUser = useCurrentUser(); 21 | const profile_id = currentUser?.profile_id || ''; 22 | 23 | return ( 24 |
25 | 26 | 27 | 28 | ( 31 | 32 | )} 33 | /> 34 | ( 37 | 41 | )} 42 | /> 43 | ( 46 | 50 | )} 51 | /> 52 | } /> 53 | } /> 54 | } /> 55 | } /> 56 | } /> 57 | } /> 58 | } /> 59 | } /> 60 | } /> 61 | 62 | } /> 63 | 64 | 65 |
66 | ); 67 | }; 68 | 69 | export default App; -------------------------------------------------------------------------------- /frontend/src/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: "DM Sans", sans-serif; 3 | background-color: #39859462; 4 | min-height: 100vh; 5 | } 6 | 7 | .Main { 8 | padding-top: 81px; 9 | } 10 | 11 | .Content { 12 | background-color: #ffffff; 13 | padding: 10px; 14 | border-radius: 2px; 15 | border: 1px solid #b8b8b89f; 16 | } 17 | 18 | .FillerImage { 19 | object-fit: cover; 20 | height: 100%; 21 | width: 100%; 22 | } 23 | 24 | .Image { 25 | max-height: 40vh; 26 | max-width: 100%; 27 | object-fit: contain; 28 | } 29 | 30 | * { 31 | box-sizing: border-box; 32 | } 33 | 34 | a { 35 | color: #555555; 36 | transition: color ease-in-out 0.3s; 37 | } 38 | 39 | a:hover { 40 | text-decoration: none; 41 | color: #1db7d9; 42 | } 43 | 44 | a:hover i { 45 | color: #1db7d9; 46 | transition: color ease-in-out 0.3s; 47 | } 48 | 49 | input[type="file"] { 50 | display: none; 51 | } 52 | 53 | i { 54 | color: #cfced3; 55 | font-size: 1.6rem; 56 | padding: 7px; 57 | vertical-align: middle; 58 | } 59 | 60 | i:hover { 61 | cursor: pointer; 62 | transition: color ease-in-out 0.3s; 63 | color: #1db7d9; 64 | } 65 | 66 | figure { 67 | text-align: center; 68 | display: inline-block; 69 | } 70 | 71 | /* scrollbar */ 72 | *::-webkit-scrollbar { 73 | width: 5px; 74 | } 75 | *::-webkit-scrollbar-track { 76 | background: #f1f1f1; 77 | } 78 | *::-webkit-scrollbar-thumb { 79 | background: #888; 80 | } 81 | *::-webkit-scrollbar-thumb:hover { 82 | background: #555; 83 | } -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | // render(); 6 | // const linkElement = screen.getByText(/learn react/i); 7 | // expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/api/axiosDefaults.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | axios.defaults.baseURL = '/api'; 4 | axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'; 5 | axios.defaults.withCredentials = true; 6 | 7 | export const axiosReq = axios.create(); 8 | export const axiosRes = axios.create(); 9 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/no-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/src/assets/no-results.png -------------------------------------------------------------------------------- /frontend/src/assets/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/frontend/src/assets/upload.png -------------------------------------------------------------------------------- /frontend/src/components/Asset.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Spinner from "react-bootstrap/Spinner"; 3 | import styles from "../styles/Asset.module.css"; 4 | 5 | const Asset = ({ spinner, src, message }) => { 6 | return ( 7 |
8 | {spinner && } 9 | {src && {message}} 10 | {message &&

{message}

} 11 |
12 | ); 13 | }; 14 | 15 | export default Asset; -------------------------------------------------------------------------------- /frontend/src/components/Avatar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../styles/Avatar.module.css"; 3 | 4 | const Avatar = ({ src, height = 45, text }) => { 5 | return ( 6 | 7 | avatar 14 | {text} 15 | 16 | ); 17 | }; 18 | 19 | export default Avatar; -------------------------------------------------------------------------------- /frontend/src/components/MoreDropdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dropdown from 'react-bootstrap/Dropdown'; 3 | import styles from '../styles/MoreDropdown.module.css'; 4 | import { useHistory } from "react-router"; 5 | 6 | // The forwardRef is important!! 7 | // Dropdown needs access to the DOM node in order to position the Menu 8 | const DropdownDots = React.forwardRef(({ onClick }, ref) => ( 9 | { 13 | e.preventDefault(); 14 | onClick(e); 15 | }} 16 | /> 17 | )); 18 | 19 | export const MoreDropdown = ({ handleEdit, handleDelete }) => { 20 | return ( 21 | 22 | 23 | 24 | 28 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export function ProfileEditDropdown({ id }) { 48 | const history = useHistory(); 49 | return ( 50 | 51 | 52 | 53 | history.push(`/profiles/${id}/edit`)} 55 | aria-label="edit-profile" 56 | > 57 | Edit Profile 58 | 59 | history.push(`/profiles/${id}/edit/username`)} 61 | aria-label="edit-username" 62 | > 63 | 64 | Change Username 65 | 66 | history.push(`/profiles/${id}/edit/password`)} 68 | aria-label="edit-password" 69 | > 70 | 71 | Change Password 72 | 73 | 74 | 75 | ); 76 | }; -------------------------------------------------------------------------------- /frontend/src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NoResults from '../assets/no-results.png'; 3 | import styles from '../styles/NotFound.module.css'; 4 | import Asset from './Asset'; 5 | 6 | const NotFound = () => { 7 | return ( 8 |
9 | 13 |
14 | ); 15 | }; 16 | 17 | export default NotFound; -------------------------------------------------------------------------------- /frontend/src/components/__tests__/NavBar.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen, fireEvent } from "@testing-library/react"; 2 | import { BrowserRouter as Router } from 'react-router-dom'; 3 | import { CurrentUserProvider } from "../../contexts/CurrentUserContext"; 4 | import NavBar from '../NavBar'; 5 | 6 | test('renders NavBar', () => { 7 | render( 8 | 9 | 10 | 11 | ); 12 | 13 | // screen.debug(); 14 | const signInLink = screen.getByRole('link', { name: 'SIGN IN' }); 15 | expect(signInLink).toBeInTheDocument(); 16 | }); 17 | 18 | test('renders link to the user profile for a logged in user', async () => { 19 | render( 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | const profileAvatar = await screen.findByText('PROFILE'); 28 | expect(profileAvatar).toBeInTheDocument(); 29 | }); 30 | 31 | test('renders SIGN IN and SIGN UP buttons again on log out', async () => { 32 | render( 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | 40 | const signOutLink = await screen.findByRole('link', { name: 'SIGN OUT' }); 41 | fireEvent.click(signOutLink); 42 | 43 | const signInLink = await screen.findByRole('link', { name: 'SIGN IN' }); 44 | const signUpLink = await screen.findByRole('link', { name: 'SIGN UP' }); 45 | 46 | expect(signInLink).toBeInTheDocument(); 47 | expect(signUpLink).toBeInTheDocument(); 48 | }); -------------------------------------------------------------------------------- /frontend/src/contexts/CurrentUserContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useMemo, useState } from "react"; 2 | import axios from "axios"; 3 | import { axiosReq, axiosRes } from "../api/axiosDefaults"; 4 | import { useHistory } from "react-router"; 5 | import { removeTokenTimestamp, shouldRefreshToken } from "../utils/utils"; 6 | 7 | export const CurrentUserContext = createContext(); 8 | export const SetCurrentUserContext = createContext(); 9 | 10 | export const useCurrentUser = () => useContext(CurrentUserContext); 11 | export const useSetCurrentUser = () => useContext(SetCurrentUserContext); 12 | 13 | export const CurrentUserProvider = ({ children }) => { 14 | const [currentUser, setCurrentUser] = useState(null); 15 | const history = useHistory(); 16 | 17 | const handleMount = async () => { 18 | try { 19 | const { data } = await axiosRes.get('dj-rest-auth/user/'); 20 | setCurrentUser(data); 21 | } catch (err) { 22 | // console.log(err); 23 | } 24 | }; 25 | 26 | useEffect(() => { 27 | handleMount(); 28 | }, []); 29 | 30 | useMemo(() => { 31 | axiosReq.interceptors.request.use( 32 | async (config) => { 33 | if (shouldRefreshToken()) { 34 | try { 35 | await axios.post('/dj-rest-auth/token/refresh/'); 36 | } catch (err) { 37 | setCurrentUser((prevCurrentUser) => { 38 | if (prevCurrentUser) { 39 | history.push('/signin'); 40 | } 41 | return null; 42 | }); 43 | removeTokenTimestamp(); 44 | return config; 45 | } 46 | } 47 | return config; 48 | }, 49 | (err) => { 50 | return Promise.reject(err); 51 | } 52 | ); 53 | 54 | axiosRes.interceptors.response.use( 55 | (response) => response, 56 | async (err) => { 57 | if (err.response?.status === 401) { 58 | try { 59 | await axios.post('dj-rest-auth/token/refresh/'); 60 | } catch (err) { 61 | setCurrentUser((prevCurrentUser) => { 62 | if (prevCurrentUser){ 63 | history.push('/signin'); 64 | } 65 | return null; 66 | }); 67 | removeTokenTimestamp(); 68 | } 69 | return axios(err.config); 70 | } 71 | return Promise.reject(err); 72 | } 73 | ); 74 | }, [history]); 75 | 76 | return ( 77 | 78 | 79 | {children} 80 | 81 | 82 | ); 83 | }; -------------------------------------------------------------------------------- /frontend/src/hooks/useClickOutsideToggle.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react' 2 | 3 | const useClickOutsideToggle = () => { 4 | const [expanded, setExpanded] = useState(false); 5 | const ref = useRef(null); 6 | useEffect(() => { 7 | const handleClickOutside = (event) => { 8 | if (ref.current && !ref.current.contains(event.target)) { 9 | setExpanded(false); 10 | } 11 | }; 12 | 13 | document.addEventListener('mouseup', handleClickOutside); 14 | return () => { 15 | document.removeEventListener('mouseup', handleClickOutside); 16 | }; 17 | }, [ref]); 18 | 19 | return { expanded, setExpanded, ref }; 20 | }; 21 | 22 | export default useClickOutsideToggle; -------------------------------------------------------------------------------- /frontend/src/hooks/useRedirect.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { useEffect } from 'react'; 3 | import { useHistory } from 'react-router'; 4 | 5 | export const useRedirect = (userAuthStatus) => { 6 | const history = useHistory(); 7 | 8 | useEffect(() => { 9 | const handleMount = async () => { 10 | try { 11 | await axios.post("/dj-rest-auth/token/refresh/"); 12 | // If user is logged in, the code below will run 13 | if (userAuthStatus === "loggedIn") { 14 | history.push("/"); 15 | } 16 | } catch (err) { 17 | // If user is not logged in, the code below will run 18 | if (userAuthStatus === "loggedOut") { 19 | history.push("/"); 20 | } 21 | } 22 | }; 23 | 24 | handleMount(); 25 | }, [history, userAuthStatus]); 26 | }; -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { BrowserRouter as Router } from "react-router-dom"; 7 | import { CurrentUserProvider } from "./contexts/CurrentUserContext"; 8 | import { ProfileDataProvider } from "./contexts/ProfileDataContext"; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | , 18 | document.getElementById("root") 19 | ); 20 | 21 | // If you want to start measuring performance in your app, pass a function 22 | // to log results (for example: reportWebVitals(console.log)) 23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 24 | reportWebVitals(); -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/mocks/handlers.js: -------------------------------------------------------------------------------- 1 | import { rest } from 'msw'; 2 | 3 | const baseURL = 'https://api-django-5-e93439fb77b5.herokuapp.com/'; 4 | 5 | export const handlers = [ 6 | rest.get(`${baseURL}dj-rest-auth/user/`, (req, res, ctx) => { 7 | return res( 8 | ctx.json({ 9 | pk: 15, 10 | username: 'wikstromphotos', 11 | email: '', 12 | first_name: '', 13 | last_name: '', 14 | profile_id: 15, 15 | profile_image: 16 | 'https://res.cloudinary.com/dsllv7c2n/image/upload/v1/media/images/Photo_1636490896993_remastered_yjfp7y', 17 | }) 18 | ); 19 | }), 20 | rest.post(`${baseURL}dj-rest-auth/logout/`, (req, res, ctx) => { 21 | return res(ctx.status(200)); 22 | }), 23 | ]; -------------------------------------------------------------------------------- /frontend/src/pages/comments/CommentCreateForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import Form from "react-bootstrap/Form"; 5 | import InputGroup from "react-bootstrap/InputGroup"; 6 | 7 | import Alert from "react-bootstrap/esm/Alert"; 8 | 9 | import styles from "../../styles/CommentCreateEditForm.module.css"; 10 | import Avatar from "../../components/Avatar"; 11 | import { axiosRes } from "../../api/axiosDefaults"; 12 | 13 | function CommentCreateForm(props) { 14 | const { post, setPost, setComments, profileImage, profile_id } = props; 15 | const [content, setContent] = useState(''); 16 | const [showSuccessAlert, setShowSuccessAlert] = useState(false); 17 | 18 | const handleChange = (event) => { 19 | setContent(event.target.value); 20 | }; 21 | 22 | const handleSubmit = async (event) => { 23 | event.preventDefault(); 24 | try { 25 | const { data } = await axiosRes.post("/comments/", { 26 | content, 27 | post, 28 | }); 29 | setComments((prevComments) => ({ 30 | ...prevComments, 31 | results: [data, ...prevComments.results], 32 | })); 33 | setPost((prevPost) => ({ 34 | results: [ 35 | { 36 | ...prevPost.results[0], 37 | comments_count: prevPost.results[0].comments_count + 1, 38 | }, 39 | ], 40 | })); 41 | setContent(''); 42 | setShowSuccessAlert(true); 43 | } catch (err) { 44 | // console.log(err); 45 | } 46 | }; 47 | 48 | return ( 49 | <> 50 | setShowSuccessAlert(false)} 54 | dismissible 55 | > 56 | Comment posted! 57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 72 | 73 | 74 | 81 |
82 | 83 | ); 84 | } 85 | 86 | export default CommentCreateForm; -------------------------------------------------------------------------------- /frontend/src/pages/comments/CommentEditForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import Form from "react-bootstrap/Form"; 4 | import { axiosRes } from "../../api/axiosDefaults"; 5 | 6 | import styles from "../../styles/CommentCreateEditForm.module.css"; 7 | 8 | function CommentEditForm(props) { 9 | const { id, content, setShowEditForm, setComments } = props; 10 | 11 | const [formContent, setFormContent] = useState(content); 12 | 13 | const handleChange = (event) => { 14 | setFormContent(event.target.value); 15 | }; 16 | 17 | const handleSubmit = async (event) => { 18 | event.preventDefault(); 19 | try { 20 | await axiosRes.put(`/comments/${id}/`, { 21 | content: formContent.trim(), 22 | }); 23 | setComments((prevComments) => ({ 24 | ...prevComments, 25 | results: prevComments.results.map((comment) => { 26 | return comment.id === id 27 | ? { 28 | ...comment, 29 | content: formContent.trim(), 30 | edited_at: 'now', 31 | } 32 | : comment; 33 | }), 34 | })); 35 | setShowEditForm(false); 36 | } catch (err) { 37 | // console.log(err); 38 | } 39 | }; 40 | 41 | return ( 42 |
43 | 44 | 51 | 52 |
53 | 60 | 67 |
68 |
69 | ); 70 | }; 71 | 72 | export default CommentEditForm; 73 | -------------------------------------------------------------------------------- /frontend/src/pages/profiles/PopularProfiles.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Container from "react-bootstrap/Container"; 3 | import appStyles from "../../App.module.css"; 4 | import Asset from "../../components/Asset"; 5 | import { useProfileData } from "../../contexts/ProfileDataContext"; 6 | import Profile from "./Profile"; 7 | 8 | const PopularProfiles = ({ mobile }) => { 9 | const { popularProfiles } = useProfileData(); 10 | 11 | return ( 12 | 17 | {popularProfiles.results.length ? ( 18 | <> 19 |

Other interesting users to check out:

20 | {mobile ? ( 21 |
22 | {popularProfiles.results.slice(0, 4).map((profile) => ( 23 | 24 | ))} 25 |
26 | ) : ( 27 | popularProfiles.results.map((profile) => ( 28 | 29 | )) 30 | )} 31 | 32 | ) : ( 33 | 34 | )} 35 |
36 | ); 37 | }; 38 | 39 | export default PopularProfiles; -------------------------------------------------------------------------------- /frontend/src/pages/profiles/Profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from '../../styles/Profile.module.css'; 3 | import btnStyles from '../../styles/Button.module.css'; 4 | import { useCurrentUser } from '../../contexts/CurrentUserContext'; 5 | import { Link } from "react-router-dom"; 6 | import Avatar from "../../components/Avatar"; 7 | import Button from 'react-bootstrap/Button'; 8 | import { useSetProfileData } from '../../contexts/ProfileDataContext'; 9 | 10 | const Profile = (props) => { 11 | const { profile, mobile, imageSize = 55 } = props; 12 | const { id, following_id, image, account_owner } = profile; 13 | 14 | const currentUser = useCurrentUser(); 15 | const is_account_owner = currentUser?.username === account_owner; 16 | 17 | const {handleFollow, handleUnfollow} = useSetProfileData(); 18 | 19 | return ( 20 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | {account_owner} 30 |
31 |
32 | {!mobile && 33 | currentUser && 34 | !is_account_owner && 35 | (following_id ? ( 36 | 42 | ) : ( 43 | 49 | ))} 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default Profile; -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | import { setupServer } from 'msw/node'; 7 | import { handlers } from './mocks/handlers'; 8 | 9 | const server = setupServer(...handlers); 10 | 11 | beforeAll(() => server.listen()); 12 | afterEach(() => server.resetHandlers()); 13 | afterAll(() => server.close()); -------------------------------------------------------------------------------- /frontend/src/styles/Asset.module.css: -------------------------------------------------------------------------------- 1 | .Asset { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | min-height: 120px; 7 | } -------------------------------------------------------------------------------- /frontend/src/styles/Avatar.module.css: -------------------------------------------------------------------------------- 1 | .Avatar { 2 | border-radius: 50%; 3 | border: 1px solid #a3a3a49f; 4 | margin: 0px 8px 0px 8px; 5 | object-fit: cover; 6 | } -------------------------------------------------------------------------------- /frontend/src/styles/Button.module.css: -------------------------------------------------------------------------------- 1 | .Button { 2 | font-size: 0.8rem; 3 | align-self: center; 4 | border-radius: 100px; 5 | border-color: transparent; 6 | padding: 4px 10px; 7 | margin-left: 2px; 8 | margin-right: 2px; 9 | min-width: 75px; 10 | } 11 | 12 | .Button:hover { 13 | opacity: 0.8; 14 | border-color: transparent; 15 | cursor: pointer; 16 | } 17 | 18 | .Button:hover:active { 19 | background-color: #1db7d9 !important; 20 | } 21 | 22 | .Button:active { 23 | background-color: #1db7d9 !important; 24 | } 25 | 26 | .Button:focus { 27 | background-color: #1db7d9; 28 | } 29 | 30 | .Wide { 31 | padding: 10px 50px; 32 | width: 100%; 33 | } 34 | 35 | .Blue, 36 | .BlueOutline:hover, 37 | .Bright:hover { 38 | background-color: #57c6dfe7; 39 | color: #3a3a3a; 40 | } 41 | 42 | .BlueOutline, 43 | .Blue:hover { 44 | background-color: #ffffff; 45 | color: #1db7d9; 46 | } 47 | 48 | .Black, 49 | .BlackOutline:hover { 50 | background-color: #2274d8; 51 | color: aliceblue; 52 | } 53 | 54 | .Black:hover, 55 | .BlackOutline { 56 | background-color: #1e68c3; 57 | color: aliceblue; 58 | } 59 | 60 | .Bright { 61 | background-color: #1db7d9; 62 | color: aliceblue; 63 | } -------------------------------------------------------------------------------- /frontend/src/styles/Comment.module.css: -------------------------------------------------------------------------------- 1 | .AccountOwner { 2 | font-weight: bold; 3 | color: #1db7d9; 4 | margin-right: 8px; 5 | } 6 | 7 | .Date { 8 | font-weight: lighter; 9 | color: #c1c1c5; 10 | } -------------------------------------------------------------------------------- /frontend/src/styles/CommentCreateEditForm.module.css: -------------------------------------------------------------------------------- 1 | .Form { 2 | border: none; 3 | border-bottom: 3px #19a1c0 solid; 4 | } 5 | 6 | .Button { 7 | border: none; 8 | background-color: #19a1c0; 9 | color: aliceblue; 10 | border-radius: 100px; 11 | padding: 4px 10px; 12 | min-width: 75px; 13 | margin: 5px; 14 | } 15 | 16 | .Button:hover { 17 | cursor: pointer; 18 | background-color: aliceblue; 19 | color: #19a1c0; 20 | } -------------------------------------------------------------------------------- /frontend/src/styles/MoreDropdown.module.css: -------------------------------------------------------------------------------- 1 | .DropdownItem { 2 | display: inline-block; 3 | max-width: 50%; 4 | padding: 0px; 5 | } 6 | 7 | .Absolute { 8 | position: absolute; 9 | right: 0; 10 | z-index: 99; 11 | } -------------------------------------------------------------------------------- /frontend/src/styles/NavBar.module.css: -------------------------------------------------------------------------------- 1 | .NavBar { 2 | background-color: #ffffff; 3 | border: 1px solid #a3a3a49f; 4 | } 5 | 6 | .NavLink { 7 | padding: 5px; 8 | } 9 | 10 | .Active i { 11 | color: #1db7d9; 12 | } -------------------------------------------------------------------------------- /frontend/src/styles/NotFound.module.css: -------------------------------------------------------------------------------- 1 | .NotFound { 2 | margin-top: 25vh; 3 | } -------------------------------------------------------------------------------- /frontend/src/styles/Post.module.css: -------------------------------------------------------------------------------- 1 | .Post { 2 | background-color: #ffffff; 3 | border: 1px solid #dadadf; 4 | border-radius: 2px; 5 | margin-bottom: 16px; 6 | text-align: center; 7 | } 8 | 9 | .Post figure, 10 | h5 { 11 | margin: 0 0; 12 | } 13 | 14 | .Heart { 15 | color: #f6162c; 16 | } 17 | 18 | .Heart:hover { 19 | color: #b21020; 20 | } 21 | 22 | .HeartOutline:hover { 23 | color: #b21020; 24 | } -------------------------------------------------------------------------------- /frontend/src/styles/PostCreateEditForm.module.css: -------------------------------------------------------------------------------- 1 | .Container { 2 | min-height: 343px; 3 | } -------------------------------------------------------------------------------- /frontend/src/styles/PostsPage.module.css: -------------------------------------------------------------------------------- 1 | .SearchBar input { 2 | border-radius: 20px; 3 | border: 1px solid #b8b8b89f; 4 | color: #242a3d; 5 | padding-left: 40px; 6 | margin-bottom: 16px; 7 | } 8 | 9 | .SearchIcon { 10 | position: absolute; 11 | font-size: 1.2rem; 12 | padding: 9px; 13 | padding-left: 15px; 14 | } 15 | 16 | .SearchIcon:hover { 17 | cursor: default; 18 | color: #cfced3; 19 | } -------------------------------------------------------------------------------- /frontend/src/styles/Profile.module.css: -------------------------------------------------------------------------------- 1 | .WordBreak { 2 | word-break: break-all; 3 | } -------------------------------------------------------------------------------- /frontend/src/styles/ProfilePage.module.css: -------------------------------------------------------------------------------- 1 | .ProfileImage { 2 | object-fit: cover; 3 | height: 120px; 4 | width: 120px; 5 | margin: 4px; 6 | border-radius: 50%; 7 | border: 1px solid #a3a3a49f; 8 | } 9 | 10 | @media screen and (max-width: 991px) { 11 | .ProfileImage { 12 | width: 250px; 13 | height: 250px; 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/src/styles/SignInUpForm.module.css: -------------------------------------------------------------------------------- 1 | .Row { 2 | height: calc(100vh - 81px); 3 | } 4 | .Input { 5 | background: #ffffff; 6 | border: 1px solid #f0f1f9; 7 | box-sizing: border-box; 8 | border-radius: 2px; 9 | color: #7177a1; 10 | text-align: center; 11 | padding: 10px; 12 | } 13 | 14 | .Header { 15 | font-style: normal; 16 | font-weight: bold; 17 | font-size: 16px; 18 | line-height: 19px; 19 | text-align: center; 20 | letter-spacing: 0.2em; 21 | text-transform: uppercase; 22 | color: #1db7d9; 23 | } 24 | 25 | .Link { 26 | text-decoration: none; 27 | text-align: center; 28 | display: block; 29 | margin: auto; 30 | } 31 | 32 | .Link span { 33 | color: #7654ff; 34 | } 35 | 36 | .Container { 37 | padding: 30px 10px; 38 | } 39 | 40 | .SignInCol { 41 | height: 320px; 42 | } 43 | 44 | .SignUpCol { 45 | height: 375px; 46 | } -------------------------------------------------------------------------------- /frontend/src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import jwtDecode from "jwt-decode"; 2 | import { axiosReq } from "../api/axiosDefaults"; 3 | 4 | export const fetchMoreData = async (resource, setResource) => { 5 | try { 6 | const { data } = await axiosReq.get(resource.next); 7 | setResource((prevResource) => ({ 8 | ...prevResource, 9 | next: data.next, 10 | results: data.results.reduce((acc, cur) => { 11 | return acc.some((accResult) => accResult.id === cur.id) 12 | ? acc 13 | : [...acc, cur]; 14 | }, prevResource.results), 15 | })); 16 | } catch (err) {} 17 | }; 18 | 19 | export const followHelper = (profile, clickedProfile, following_id) => { 20 | return profile.id === clickedProfile.id 21 | ? // The clicked profile, update followers count and set following id 22 | { 23 | ...profile, 24 | followers_count: profile.followers_count + 1, 25 | following_id, 26 | } 27 | : profile.is_account_owner 28 | ? // The logged in users profile, update it's following count 29 | { ...profile, following_count: profile.following_count + 1 } 30 | : // Not the profile the user clicked on or the profile the user owns 31 | // Return it unchanged 32 | profile; 33 | }; 34 | 35 | export const unfollowHelper = (profile, clickedProfile) => { 36 | return profile.id === clickedProfile.id 37 | ? // The clicked profile, update followers count and set following id 38 | { 39 | ...profile, 40 | followers_count: profile.followers_count - 1, 41 | following_id: null, 42 | } 43 | : profile.is_account_owner 44 | ? // The logged in users profile, update it's following count 45 | { ...profile, following_count: profile.following_count - 1 } 46 | : // Not the profile the user clicked on or the profile the user owns 47 | // Return it unchanged 48 | profile; 49 | }; 50 | 51 | export const setTokenTimestamp = (data) => { 52 | const refreshTokenTimestamp = jwtDecode(data?.refresh_token).exp; 53 | localStorage.setItem('refreshTokenTimestamp', refreshTokenTimestamp); 54 | }; 55 | 56 | export const shouldRefreshToken = () => { 57 | return !!localStorage.getItem('refreshTokenTimestamp'); 58 | }; 59 | 60 | export const removeTokenTimestamp = () => { 61 | localStorage.removeItem('refreshTokenTimestamp'); 62 | }; -------------------------------------------------------------------------------- /likes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/likes/__init__.py -------------------------------------------------------------------------------- /likes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /likes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LikesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'likes' 7 | -------------------------------------------------------------------------------- /likes/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2024-01-28 17:04 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('posts', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Like', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('made_at', models.DateTimeField(auto_now_add=True)), 23 | ('account_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 24 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='posts.post')), 25 | ], 26 | options={ 27 | 'ordering': ['-made_at'], 28 | 'unique_together': {('account_owner', 'post')}, 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /likes/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/likes/migrations/__init__.py -------------------------------------------------------------------------------- /likes/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from posts.models import Post 4 | 5 | 6 | class Like(models.Model): 7 | post = models.ForeignKey(Post, related_name='likes', on_delete=models.CASCADE) 8 | account_owner = models.ForeignKey(User, on_delete=models.CASCADE) 9 | made_at = models.DateTimeField(auto_now_add=True) 10 | 11 | class Meta: 12 | ordering = ['-made_at'] 13 | unique_together = ['account_owner', 'post'] 14 | 15 | def __str__(self): 16 | return f'{self.account_owner} {self.post}' -------------------------------------------------------------------------------- /likes/serializers.py: -------------------------------------------------------------------------------- 1 | from django.db import IntegrityError 2 | from rest_framework import serializers 3 | from likes.models import Like 4 | 5 | 6 | class LikeSerializer(serializers.ModelSerializer): 7 | account_owner = serializers.ReadOnlyField(source='account_owner.username') 8 | 9 | class Meta: 10 | model = Like 11 | fields = ['id', 'made_at', 'account_owner', 'post'] 12 | 13 | 14 | def create(self, validated_data): 15 | try: 16 | return super().create(validated_data) 17 | except IntegrityError: 18 | raise serializers.ValidationError({'detail': 'possible duplicate'}) 19 | 20 | -------------------------------------------------------------------------------- /likes/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /likes/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from likes import views 3 | 4 | urlpatterns = [ 5 | path('likes/', views.LikeList.as_view()), 6 | path('likes//', views.LikeDetail.as_view()), 7 | ] 8 | -------------------------------------------------------------------------------- /likes/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, permissions 2 | from drf_api.permissions import IsAccountOwnerOrReadOnly 3 | from .models import Like 4 | from .serializers import LikeSerializer 5 | 6 | 7 | class LikeList(generics.ListCreateAPIView): 8 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 9 | serializer_class = LikeSerializer 10 | queryset = Like.objects.all() 11 | 12 | def perform_create(self, serializer): 13 | serializer.save(account_owner=self.request.user) 14 | 15 | 16 | class LikeDetail(generics.RetrieveDestroyAPIView): 17 | permission_classes = [IsAccountOwnerOrReadOnly] 18 | queryset = Like.objects.all() 19 | serializer_class = LikeSerializer 20 | 21 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_api.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "API-Django", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /posts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/posts/__init__.py -------------------------------------------------------------------------------- /posts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /posts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PostsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'posts' 7 | -------------------------------------------------------------------------------- /posts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2024-01-28 17:04 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Post', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('made_at', models.DateTimeField(auto_now_add=True)), 22 | ('edited_at', models.DateTimeField(auto_now=True)), 23 | ('title', models.CharField(max_length=255)), 24 | ('content', models.TextField(blank=True)), 25 | ('image', models.ImageField(blank=True, default='../default_post_hde9ti', upload_to='images/')), 26 | ('image_filter', models.CharField(choices=[('_1977', '1977'), ('brannan', 'Brannan'), ('earlybird', 'Earlybird'), ('hudson', 'Hudson'), ('inkwell', 'Inkwell'), ('lofi', 'Lo-Fi'), ('kelvin', 'Kelvin'), ('normal', 'Normal'), ('nashville', 'Nashville'), ('rise', 'Rise'), ('toaster', 'Toaster'), ('valencia', 'Valencia'), ('walden', 'Walden'), ('xpro2', 'X-pro II')], default='normal', max_length=32)), 27 | ('account_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | options={ 30 | 'ordering': ['-made_at'], 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /posts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/posts/migrations/__init__.py -------------------------------------------------------------------------------- /posts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Post(models.Model): 6 | 7 | image_filter_choices = [ 8 | ('_1977', '1977'), 9 | ('brannan', 'Brannan'), 10 | ('earlybird', 'Earlybird'), 11 | ('hudson', 'Hudson'), 12 | ('inkwell', 'Inkwell'), 13 | ('lofi', 'Lo-Fi'), 14 | ('kelvin', 'Kelvin'), 15 | ('normal', 'Normal'), 16 | ('nashville', 'Nashville'), 17 | ('rise', 'Rise'), 18 | ('toaster', 'Toaster'), 19 | ('valencia', 'Valencia'), 20 | ('walden', 'Walden'), 21 | ('xpro2', 'X-pro II') 22 | ] 23 | account_owner = models.ForeignKey(User, on_delete=models.CASCADE) 24 | image = models.ImageField(upload_to='images/', default='../default_post_hde9ti', blank=True) 25 | image_filter = models.CharField(max_length=32, choices=image_filter_choices, default='normal') 26 | title = models.CharField(max_length=255) 27 | content = models.TextField(blank=True) 28 | made_at = models.DateTimeField(auto_now_add=True) 29 | edited_at = models.DateTimeField(auto_now=True) 30 | 31 | class Meta: 32 | ordering = ['-made_at'] 33 | 34 | def __str__(self): 35 | return f'{self.id} {self.title}' -------------------------------------------------------------------------------- /posts/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from posts.models import Post 3 | from likes.models import Like 4 | 5 | 6 | class PostSerializer(serializers.ModelSerializer): 7 | account_owner = serializers.ReadOnlyField(source='account_owner.username') 8 | is_account_owner = serializers.SerializerMethodField() 9 | profile_id = serializers.ReadOnlyField(source='account_owner.profile.id') 10 | profile_image = serializers.ReadOnlyField(source='account_owner.profile.image.url') 11 | like_id = serializers.SerializerMethodField() 12 | likes_count = serializers.ReadOnlyField() 13 | comments_count = serializers.ReadOnlyField() 14 | 15 | def validate_image(self, value): 16 | if value.size > 2 * 1024 * 1024: 17 | raise serializers.ValidationError('Image size larger than 2MB!') 18 | if value.image.height > 4096: 19 | raise serializers.ValidationError( 20 | 'Image height larger than 4096px!' 21 | ) 22 | if value.image.width > 4096: 23 | raise serializers.ValidationError( 24 | 'Image width larger than 4096px!' 25 | ) 26 | return value 27 | 28 | def get_is_account_owner(self, obj): 29 | request = self.context['request'] 30 | return request.user == obj.account_owner 31 | 32 | def get_like_id(self, obj): 33 | user = self.context['request'].user 34 | if user.is_authenticated: 35 | like = Like.objects.filter( 36 | account_owner=user, post=obj 37 | ).first() 38 | return like.id if like else None 39 | return None 40 | 41 | class Meta: 42 | model = Post 43 | fields = [ 44 | 'id', 'account_owner', 'is_account_owner', 'profile_id', 45 | 'profile_image', 'made_at', 'edited_at', 46 | 'title', 'content', 'image', 'image_filter', 47 | 'like_id', 'likes_count', 'comments_count', 48 | ] -------------------------------------------------------------------------------- /posts/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from .models import Post 3 | from rest_framework import status 4 | from rest_framework.test import APITestCase 5 | 6 | 7 | class PostListViewTests(APITestCase): 8 | def setUp(self): 9 | User.objects.create_user(username='adam', password='pass') 10 | 11 | def test_can_list_posts(self): 12 | adam = User.objects.get(username='adam') 13 | Post.objects.create(owner=adam, title='a title') 14 | response = self.client.get('/posts/') 15 | self.assertEqual(response.status_code, status.HTTP_200_OK) 16 | print(response.data) 17 | print(len(response.data)) 18 | 19 | def test_logged_in_user_can_create_post(self): 20 | self.client.login(username='adam', password='pass') 21 | response = self.client.post('/posts/', {'title': 'a title'}) 22 | count = Post.objects.count() 23 | self.assertEqual(count, 1) 24 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 25 | 26 | def test_user_not_logged_in_cant_create_post(self): 27 | response = self.client.post('/posts/', {'title': 'a title'}) 28 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 29 | 30 | 31 | class PostDetailViewTests(APITestCase): 32 | def setUp(self): 33 | adam = User.objects.create_user(username='adam', password='pass') 34 | brian = User.objects.create_user(username='brian', password='pass') 35 | Post.objects.create( 36 | owner=adam, title='a title', content='adams content' 37 | ) 38 | Post.objects.create( 39 | owner=brian, title='another title', content='brians content' 40 | ) 41 | 42 | def test_can_retrieve_post_using_valid_id(self): 43 | response = self.client.get('/posts/1/') 44 | self.assertEqual(response.data['title'], 'a title') 45 | self.assertEqual(response.status_code, status.HTTP_200_OK) 46 | 47 | def test_cant_retrieve_post_using_invalid_id(self): 48 | response = self.client.get('/posts/999/') 49 | self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) 50 | 51 | def test_user_can_update_own_post(self): 52 | self.client.login(username='adam', password='pass') 53 | response = self.client.put('/posts/1/', {'title': 'a new title'}) 54 | post = Post.objects.filter(pk=1).first() 55 | self.assertEqual(post.title, 'a new title') 56 | self.assertEqual(response.status_code, status.HTTP_200_OK) 57 | 58 | def test_user_cant_update_another_users_post(self): 59 | self.client.login(username='adam', password='pass') 60 | response = self.client.put('/posts/2/', {'title': 'a new title'}) 61 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) -------------------------------------------------------------------------------- /posts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from posts import views 3 | 4 | urlpatterns = [ 5 | path('posts/', views.PostList.as_view()), 6 | path('posts//', views.PostDetail.as_view()) 7 | ] -------------------------------------------------------------------------------- /posts/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Count 2 | from rest_framework import generics, permissions, filters 3 | from django_filters.rest_framework import DjangoFilterBackend 4 | from drf_api.permissions import IsAccountOwnerOrReadOnly 5 | from .models import Post 6 | from .serializers import PostSerializer 7 | 8 | 9 | class PostList(generics.ListCreateAPIView): 10 | serializer_class = PostSerializer 11 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 12 | queryset = Post.objects.annotate( 13 | likes_count=Count('likes', distinct=True), 14 | comments_count=Count('comment', distinct=True) 15 | ).order_by('-made_at') 16 | filter_backends = [ 17 | filters.OrderingFilter, 18 | filters.SearchFilter, 19 | DjangoFilterBackend, 20 | ] 21 | filterset_fields = [ 22 | 'account_owner__followed__account_owner__profile', 23 | 'likes__account_owner__profile', 24 | 'account_owner__profile', 25 | ] 26 | search_fields = [ 27 | 'account_owner__username', 28 | 'title', 29 | ] 30 | ordering_fields = [ 31 | 'likes_count', 32 | 'comments_count', 33 | 'likes__made_at', 34 | ] 35 | 36 | def perform_create(self, serializer): 37 | serializer.save(account_owner=self.request.user) 38 | 39 | 40 | class PostDetail(generics.RetrieveUpdateDestroyAPIView): 41 | serializer_class = PostSerializer 42 | permission_classes = [IsAccountOwnerOrReadOnly] 43 | queryset = Post.objects.annotate( 44 | likes_count=Count('likes', distinct=True), 45 | comments_count=Count('comment', distinct=True) 46 | ).order_by('-made_at') -------------------------------------------------------------------------------- /profiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/profiles/__init__.py -------------------------------------------------------------------------------- /profiles/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Profile 3 | 4 | admin.site.register(Profile) 5 | -------------------------------------------------------------------------------- /profiles/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProfilesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'profiles' 7 | -------------------------------------------------------------------------------- /profiles/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2024-01-28 17:04 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Profile', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('made_at', models.DateTimeField(auto_now_add=True)), 22 | ('edited_at', models.DateTimeField(auto_now=True)), 23 | ('name', models.CharField(blank=True, max_length=255)), 24 | ('content', models.TextField(blank=True)), 25 | ('image', models.ImageField(default='../default_profile_ftyuqx', upload_to='images/')), 26 | ('account_owner', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 27 | ], 28 | options={ 29 | 'ordering': ['-made_at'], 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /profiles/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/profiles/migrations/__init__.py -------------------------------------------------------------------------------- /profiles/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import post_save 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class Profile(models.Model): 7 | image = models.ImageField(upload_to='images/', default='../default_profile_ftyuqx') 8 | account_owner = models.OneToOneField(User, on_delete=models.CASCADE) 9 | name = models.CharField(max_length=255, blank=True) 10 | content = models.TextField(blank=True) 11 | made_at = models.DateTimeField(auto_now_add=True) 12 | edited_at = models.DateTimeField(auto_now=True) 13 | 14 | class Meta: 15 | ordering = ['-made_at'] 16 | 17 | def __str__(self): 18 | return f"{self.account_owner}'s profile" 19 | 20 | 21 | def create_profile(sender, instance, created, **kwargs): 22 | if created: 23 | Profile.objects.create(account_owner=instance) 24 | 25 | post_save.connect(create_profile, sender=User) 26 | -------------------------------------------------------------------------------- /profiles/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Profile 3 | from followers.models import Follower 4 | 5 | 6 | class ProfileSerializer(serializers.ModelSerializer): 7 | account_owner = serializers.ReadOnlyField(source='account_owner.username') 8 | is_account_owner = serializers.SerializerMethodField() 9 | following_id = serializers.SerializerMethodField() 10 | posts_count = serializers.ReadOnlyField() 11 | followers_count = serializers.ReadOnlyField() 12 | following_count = serializers.ReadOnlyField() 13 | 14 | def get_is_account_owner(self, obj): 15 | request = self.context['request'] 16 | return request.user == obj.account_owner 17 | 18 | def get_following_id(self, obj): 19 | user = self.context['request'].user 20 | if user.is_authenticated: 21 | following = Follower.objects.filter( 22 | account_owner=user, followed=obj.account_owner 23 | ).first() 24 | # print(following) 25 | return following.id if following else None 26 | return None 27 | 28 | class Meta: 29 | model = Profile 30 | fields = [ 31 | 'id', 'account_owner', 'made_at', 'edited_at', 'name', 32 | 'content', 'image', 'is_account_owner', 'following_id', 33 | 'posts_count', 'followers_count', 'following_count', 34 | ] -------------------------------------------------------------------------------- /profiles/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /profiles/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from profiles import views 3 | 4 | urlpatterns = [ 5 | path('profiles/', views.ProfileList.as_view()), 6 | path('profiles//', views.ProfileDetail.as_view()), 7 | ] -------------------------------------------------------------------------------- /profiles/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Count 2 | from rest_framework import generics, filters 3 | from django_filters.rest_framework import DjangoFilterBackend 4 | from drf_api.permissions import IsAccountOwnerOrReadOnly 5 | from .models import Profile 6 | from .serializers import ProfileSerializer 7 | 8 | 9 | class ProfileList(generics.ListAPIView): 10 | queryset = Profile.objects.annotate( 11 | posts_count=Count('account_owner__post', distinct=True), 12 | followers_count=Count('account_owner__followed', distinct=True), 13 | following_count=Count('account_owner__following', distinct=True) 14 | ).order_by('-made_at') 15 | serializer_class = ProfileSerializer 16 | filter_backends = [ 17 | filters.OrderingFilter, 18 | DjangoFilterBackend, 19 | ] 20 | filterset_fields = [ 21 | 'account_owner__following__followed__profile', 22 | 'account_owner__followed__account_owner__profile', 23 | ] 24 | ordering_fields = [ 25 | 'posts_count', 26 | 'followers_count', 27 | 'following_count', 28 | 'account_owner__following__made_at', 29 | 'account_owner__followed__made_at', 30 | ] 31 | 32 | 33 | class ProfileDetail(generics.RetrieveUpdateAPIView): 34 | permission_classes = [IsAccountOwnerOrReadOnly] 35 | queryset = Profile.objects.annotate( 36 | posts_count=Count('account_owner__post', distinct=True), 37 | followers_count=Count('account_owner__followed', distinct=True), 38 | following_count=Count('account_owner__following', distinct=True) 39 | ).order_by('-made_at') 40 | serializer_class = ProfileSerializer -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | cloudinary==1.33.0 3 | dj-database-url==0.5.0 4 | dj-rest-auth==2.1.9 5 | Django==3.2.20 6 | django-allauth==0.44.0 7 | django-cloudinary-storage==0.3.0 8 | django-cors-headers==4.2.0 9 | django-filter==23.2 10 | djangorestframework==3.14.0 11 | djangorestframework-simplejwt==4.7.2 12 | gunicorn==20.1.0 13 | oauthlib==3.2.2 14 | Pillow==8.2.0 15 | psycopg2==2.9.6 16 | PyJWT==2.7.0 17 | python3-openid==3.2.0 18 | pytz==2023.3 19 | requests-oauthlib==1.3.1 20 | sqlparse==0.4.4 21 | urllib3==1.26.16 22 | whitenoise==6.4.0 23 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.16 -------------------------------------------------------------------------------- /staticfiles/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 | } 27 | -------------------------------------------------------------------------------- /staticfiles/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 | -------------------------------------------------------------------------------- /staticfiles/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | .login { 4 | background: var(--darkened-bg); 5 | height: auto; 6 | } 7 | 8 | .login #header { 9 | height: auto; 10 | padding: 15px 16px; 11 | justify-content: center; 12 | } 13 | 14 | .login #header h1 { 15 | font-size: 18px; 16 | } 17 | 18 | .login #header h1 a { 19 | color: var(--header-link-color); 20 | } 21 | 22 | .login #content { 23 | padding: 20px 20px 0; 24 | } 25 | 26 | .login #container { 27 | background: var(--body-bg); 28 | border: 1px solid var(--hairline-color); 29 | border-radius: 4px; 30 | overflow: hidden; 31 | width: 28em; 32 | min-width: 300px; 33 | margin: 100px auto; 34 | height: auto; 35 | } 36 | 37 | .login .form-row { 38 | padding: 4px 0; 39 | } 40 | 41 | .login .form-row label { 42 | display: block; 43 | line-height: 2em; 44 | } 45 | 46 | .login .form-row #id_username, .login .form-row #id_password { 47 | padding: 8px; 48 | width: 100%; 49 | box-sizing: border-box; 50 | } 51 | 52 | .login .submit-row { 53 | padding: 1em 0 0 0; 54 | margin: 0; 55 | text-align: center; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /staticfiles/admin/css/nav_sidebar.css: -------------------------------------------------------------------------------- 1 | .sticky { 2 | position: sticky; 3 | top: 0; 4 | max-height: 100vh; 5 | } 6 | 7 | .toggle-nav-sidebar { 8 | z-index: 20; 9 | left: 0; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | flex: 0 0 23px; 14 | width: 23px; 15 | border: 0; 16 | border-right: 1px solid var(--hairline-color); 17 | background-color: var(--body-bg); 18 | cursor: pointer; 19 | font-size: 20px; 20 | color: var(--link-fg); 21 | padding: 0; 22 | } 23 | 24 | [dir="rtl"] .toggle-nav-sidebar { 25 | border-left: 1px solid var(--hairline-color); 26 | border-right: 0; 27 | } 28 | 29 | .toggle-nav-sidebar:hover, 30 | .toggle-nav-sidebar:focus { 31 | background-color: var(--darkened-bg); 32 | } 33 | 34 | #nav-sidebar { 35 | z-index: 15; 36 | flex: 0 0 275px; 37 | left: -276px; 38 | margin-left: -276px; 39 | border-top: 1px solid transparent; 40 | border-right: 1px solid var(--hairline-color); 41 | background-color: var(--body-bg); 42 | overflow: auto; 43 | } 44 | 45 | [dir="rtl"] #nav-sidebar { 46 | border-left: 1px solid var(--hairline-color); 47 | border-right: 0; 48 | left: 0; 49 | margin-left: 0; 50 | right: -276px; 51 | margin-right: -276px; 52 | } 53 | 54 | .toggle-nav-sidebar::before { 55 | content: '\00BB'; 56 | } 57 | 58 | .main.shifted .toggle-nav-sidebar::before { 59 | content: '\00AB'; 60 | } 61 | 62 | .main.shifted > #nav-sidebar { 63 | left: 24px; 64 | margin-left: 0; 65 | } 66 | 67 | [dir="rtl"] .main.shifted > #nav-sidebar { 68 | left: 0; 69 | right: 24px; 70 | margin-right: 0; 71 | } 72 | 73 | #nav-sidebar .module th { 74 | width: 100%; 75 | overflow-wrap: anywhere; 76 | } 77 | 78 | #nav-sidebar .module th, 79 | #nav-sidebar .module caption { 80 | padding-left: 16px; 81 | } 82 | 83 | #nav-sidebar .module td { 84 | white-space: nowrap; 85 | } 86 | 87 | [dir="rtl"] #nav-sidebar .module th, 88 | [dir="rtl"] #nav-sidebar .module caption { 89 | padding-left: 8px; 90 | padding-right: 16px; 91 | } 92 | 93 | #nav-sidebar .current-app .section:link, 94 | #nav-sidebar .current-app .section:visited { 95 | color: var(--header-color); 96 | font-weight: bold; 97 | } 98 | 99 | #nav-sidebar .current-model { 100 | background: var(--selected-row); 101 | } 102 | 103 | .main > #nav-sidebar + .content { 104 | max-width: calc(100% - 23px); 105 | } 106 | 107 | .main.shifted > #nav-sidebar + .content { 108 | max-width: calc(100% - 299px); 109 | } 110 | 111 | @media (max-width: 767px) { 112 | #nav-sidebar, #toggle-nav-sidebar { 113 | display: none; 114 | } 115 | 116 | .main > #nav-sidebar + .content, 117 | .main.shifted > #nav-sidebar + .content { 118 | max-width: 100%; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /staticfiles/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 | [dir="rtl"] #changelist-filter { 28 | margin-left: 0; 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"] .aligned .related-lookup, 67 | [dir="rtl"] .aligned .datetimeshortcuts { 68 | margin-left: 0; 69 | margin-right: 15px; 70 | } 71 | 72 | [dir="rtl"] .aligned ul { 73 | margin-right: 0; 74 | } 75 | 76 | [dir="rtl"] #changelist-filter { 77 | margin-left: 0; 78 | margin-right: 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /staticfiles/admin/css/vendor/select2/LICENSE-SELECT2.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 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 | -------------------------------------------------------------------------------- /staticfiles/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 | -------------------------------------------------------------------------------- /staticfiles/admin/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/admin/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /staticfiles/admin/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/admin/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /staticfiles/admin/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/admin/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /staticfiles/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 | -------------------------------------------------------------------------------- /staticfiles/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 | -------------------------------------------------------------------------------- /staticfiles/admin/img/calendar-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /staticfiles/admin/img/gis/move_vertex_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /staticfiles/admin/img/gis/move_vertex_on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-changelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-unknown-alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-viewlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/icon-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/inline-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/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 | -------------------------------------------------------------------------------- /staticfiles/admin/img/tooltag-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/img/tooltag-arrowright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /staticfiles/admin/js/autocomplete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const $ = django.jQuery; 4 | const init = function($element, options) { 5 | const settings = $.extend({ 6 | ajax: { 7 | data: function(params) { 8 | return { 9 | term: params.term, 10 | page: params.page, 11 | app_label: $element.data('app-label'), 12 | model_name: $element.data('model-name'), 13 | field_name: $element.data('field-name') 14 | }; 15 | } 16 | } 17 | }, options); 18 | $element.select2(settings); 19 | }; 20 | 21 | $.fn.djangoAdminSelect2 = function(options) { 22 | const settings = $.extend({}, options); 23 | $.each(this, function(i, element) { 24 | const $element = $(element); 25 | init($element, settings); 26 | }); 27 | return this; 28 | }; 29 | 30 | $(function() { 31 | // Initialize all autocomplete widgets except the one in the template 32 | // form used when a new formset is added. 33 | $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2(); 34 | }); 35 | 36 | $(document).on('formset:added', (function() { 37 | return function(event, $newFormset) { 38 | return $newFormset.find('.admin-autocomplete').djangoAdminSelect2(); 39 | }; 40 | })(this)); 41 | } 42 | -------------------------------------------------------------------------------- /staticfiles/admin/js/cancel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | // Call function fn when the DOM is loaded and ready. If it is already 4 | // loaded, call the function now. 5 | // http://youmightnotneedjquery.com/#ready 6 | function ready(fn) { 7 | if (document.readyState !== 'loading') { 8 | fn(); 9 | } else { 10 | document.addEventListener('DOMContentLoaded', fn); 11 | } 12 | } 13 | 14 | ready(function() { 15 | function handleClick(event) { 16 | event.preventDefault(); 17 | const params = new URLSearchParams(window.location.search); 18 | if (params.has('_popup')) { 19 | window.close(); // Close the popup. 20 | } else { 21 | window.history.back(); // Otherwise, go back. 22 | } 23 | } 24 | 25 | document.querySelectorAll('.cancel-link').forEach(function(el) { 26 | el.addEventListener('click', handleClick); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /staticfiles/admin/js/change_form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const inputTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; 4 | const modelName = document.getElementById('django-admin-form-add-constants').dataset.modelName; 5 | if (modelName) { 6 | const form = document.getElementById(modelName + '_form'); 7 | for (const element of form.elements) { 8 | // HTMLElement.offsetParent returns null when the element is not 9 | // rendered. 10 | if (inputTags.includes(element.tagName) && !element.disabled && element.offsetParent) { 11 | element.focus(); 12 | break; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /staticfiles/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | /*global gettext*/ 2 | 'use strict'; 3 | { 4 | window.addEventListener('load', function() { 5 | // Add anchor tag for Show/Hide link 6 | const fieldsets = document.querySelectorAll('fieldset.collapse'); 7 | for (const [i, elem] of fieldsets.entries()) { 8 | // Don't hide if fields in this fieldset have errors 9 | if (elem.querySelectorAll('div.errors, ul.errorlist').length === 0) { 10 | elem.classList.add('collapsed'); 11 | const h2 = elem.querySelector('h2'); 12 | const link = document.createElement('a'); 13 | link.id = 'fieldsetcollapser' + i; 14 | link.className = 'collapse-toggle'; 15 | link.href = '#'; 16 | link.textContent = gettext('Show'); 17 | h2.appendChild(document.createTextNode(' (')); 18 | h2.appendChild(link); 19 | h2.appendChild(document.createTextNode(')')); 20 | } 21 | } 22 | // Add toggle to hide/show anchor tag 23 | const toggleFunc = function(ev) { 24 | if (ev.target.matches('.collapse-toggle')) { 25 | ev.preventDefault(); 26 | ev.stopPropagation(); 27 | const fieldset = ev.target.closest('fieldset'); 28 | if (fieldset.classList.contains('collapsed')) { 29 | // Show 30 | ev.target.textContent = gettext('Hide'); 31 | fieldset.classList.remove('collapsed'); 32 | } else { 33 | // Hide 34 | ev.target.textContent = gettext('Show'); 35 | fieldset.classList.add('collapsed'); 36 | } 37 | } 38 | }; 39 | document.querySelectorAll('fieldset.module').forEach(function(el) { 40 | el.addEventListener('click', toggleFunc); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /staticfiles/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /*global jQuery:false*/ 2 | 'use strict'; 3 | /* Puts the included jQuery into our own namespace using noConflict and passing 4 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 5 | * namespace (i.e. this preserves pre-existing values for both window.$ and 6 | * window.jQuery). 7 | */ 8 | window.django = {jQuery: jQuery.noConflict(true)}; 9 | -------------------------------------------------------------------------------- /staticfiles/admin/js/nav_sidebar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const toggleNavSidebar = document.getElementById('toggle-nav-sidebar'); 4 | if (toggleNavSidebar !== null) { 5 | const navLinks = document.querySelectorAll('#nav-sidebar a'); 6 | function disableNavLinkTabbing() { 7 | for (const navLink of navLinks) { 8 | navLink.tabIndex = -1; 9 | } 10 | } 11 | function enableNavLinkTabbing() { 12 | for (const navLink of navLinks) { 13 | navLink.tabIndex = 0; 14 | } 15 | } 16 | 17 | const main = document.getElementById('main'); 18 | let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen'); 19 | if (navSidebarIsOpen === null) { 20 | navSidebarIsOpen = 'true'; 21 | } 22 | if (navSidebarIsOpen === 'false') { 23 | disableNavLinkTabbing(); 24 | } 25 | main.classList.toggle('shifted', navSidebarIsOpen === 'true'); 26 | 27 | toggleNavSidebar.addEventListener('click', function() { 28 | if (navSidebarIsOpen === 'true') { 29 | navSidebarIsOpen = 'false'; 30 | disableNavLinkTabbing(); 31 | } else { 32 | navSidebarIsOpen = 'true'; 33 | enableNavLinkTabbing(); 34 | } 35 | localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen); 36 | main.classList.toggle('shifted'); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /staticfiles/admin/js/popup_response.js: -------------------------------------------------------------------------------- 1 | /*global opener */ 2 | 'use strict'; 3 | { 4 | const 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 | -------------------------------------------------------------------------------- /staticfiles/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | /*global URLify*/ 2 | 'use strict'; 3 | { 4 | const $ = django.jQuery; 5 | $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { 6 | /* 7 | Depends on urlify.js 8 | Populates a selected field with the values of the dependent fields, 9 | URLifies and shortens the string. 10 | dependencies - array of dependent fields ids 11 | maxLength - maximum length of the URLify'd string 12 | allowUnicode - Unicode support of the URLify'd string 13 | */ 14 | return this.each(function() { 15 | const prepopulatedField = $(this); 16 | 17 | const populate = function() { 18 | // Bail if the field's value has been changed by the user 19 | if (prepopulatedField.data('_changed')) { 20 | return; 21 | } 22 | 23 | const values = []; 24 | $.each(dependencies, function(i, field) { 25 | field = $(field); 26 | if (field.val().length > 0) { 27 | values.push(field.val()); 28 | } 29 | }); 30 | prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); 31 | }; 32 | 33 | prepopulatedField.data('_changed', false); 34 | prepopulatedField.on('change', function() { 35 | prepopulatedField.data('_changed', true); 36 | }); 37 | 38 | if (!prepopulatedField.val()) { 39 | $(dependencies.join(',')).on('keyup change focus', populate); 40 | } 41 | }); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /staticfiles/admin/js/prepopulate_init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const $ = django.jQuery; 4 | const fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields'); 5 | $.each(fields, function(index, field) { 6 | $('.empty-form .form-row .field-' + field.name + ', .empty-form.form-row .field-' + field.name).addClass('prepopulated_field'); 7 | $(field.id).data('dependency_list', field.dependency_list).prepopulate( 8 | field.dependency_ids, field.maxLength, field.allowUnicode 9 | ); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 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 | -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/af.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Verwyders asseblief "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Voer asseblief "+(e.minimum-e.input.length)+" of meer karakters"},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var n="Kies asseblief net "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"},removeAllItems:function(){return"Verwyder alle items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ar.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(n){return"الرجاء حذف "+(n.input.length-n.maximum)+" عناصر"},inputTooShort:function(n){return"الرجاء إضافة "+(n.minimum-n.input.length)+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(n){return"تستطيع إختيار "+n.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"},removeAllItems:function(){return"قم بإزالة كل العناصر"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/az.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/az",[],function(){return{inputTooLong:function(n){return n.input.length-n.maximum+" simvol silin"},inputTooShort:function(n){return n.minimum-n.input.length+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(n){return"Sadəcə "+n.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"},removeAllItems:function(){return"Bütün elementləri sil"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/bg.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bg",[],function(){return{inputTooLong:function(n){var e=n.input.length-n.maximum,u="Моля въведете с "+e+" по-малко символ";return e>1&&(u+="a"),u},inputTooShort:function(n){var e=n.minimum-n.input.length,u="Моля въведете още "+e+" символ";return e>1&&(u+="a"),u},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(n){var e="Можете да направите до "+n.maximum+" ";return n.maximum>1?e+="избора":e+="избор",e},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"},removeAllItems:function(){return"Премахнете всички елементи"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/bn.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bn",[],function(){return{errorLoading:function(){return"ফলাফলগুলি লোড করা যায়নি।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।";return 1!=e&&(u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।"),u},inputTooShort:function(n){return n.minimum-n.input.length+" টি অক্ষর অথবা অধিক অক্ষর লিখুন।"},loadingMore:function(){return"আরো ফলাফল লোড হচ্ছে ..."},maximumSelected:function(n){var e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।";return 1!=n.maximum&&(e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।"),e},noResults:function(){return"কোন ফলাফল পাওয়া যায়নি।"},searching:function(){return"অনুসন্ধান করা হচ্ছে ..."}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/bs.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/bs",[],function(){function e(e,n,r,t){return e%10==1&&e%100!=11?n:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(n){var r=n.input.length-n.maximum,t="Obrišite "+r+" simbol";return t+=e(r,"","a","a")},inputTooShort:function(n){var r=n.minimum-n.input.length,t="Ukucajte bar još "+r+" simbol";return t+=e(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(n){var r="Možete izabrati samo "+n.maximum+" stavk";return r+=e(n.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Uklonite sve stavke"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ca.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Si us plau, elimina "+n+" car";return r+=1==n?"àcter":"àcters"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Si us plau, introdueix "+n+" car";return r+=1==n?"àcter":"àcters"},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var n="Només es pot seleccionar "+e.maximum+" element";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"},removeAllItems:function(){return"Treu tots els elements"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/cs.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/cs",[],function(){function e(e,n){switch(e){case 2:return n?"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(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadejte o jeden znak méně.":t<=4?"Prosím, zadejte o "+e(t,!0)+" znaky méně.":"Prosím, zadejte o "+t+" znaků méně."},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadejte ještě jeden znak.":t<=4?"Prosím, zadejte ještě další "+e(t,!0)+" znaky.":"Prosím, zadejte ještě dalších "+t+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(n){var t=n.maximum;return 1==t?"Můžete zvolit jen jednu položku.":t<=4?"Můžete zvolit maximálně "+e(t,!1)+" položky.":"Můžete zvolit maximálně "+t+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"},removeAllItems:function(){return"Odstraňte všechny položky"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/da.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){return"Angiv venligst "+(e.input.length-e.maximum)+" tegn mindre"},inputTooShort:function(e){return"Angiv venligst "+(e.minimum-e.input.length)+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var n="Du kan kun vælge "+e.maximum+" emne";return 1!=e.maximum&&(n+="r"),n},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/de.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){return"Bitte "+(e.input.length-e.maximum)+" Zeichen weniger eingeben"},inputTooShort:function(e){return"Bitte "+(e.minimum-e.input.length)+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var n="Sie können nur "+e.maximum+" Element";return 1!=e.maximum&&(n+="e"),n+=" auswählen"},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"},removeAllItems:function(){return"Entferne alle Elemente"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/dsb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/dsb",[],function(){var n=["znamuško","znamušce","znamuška","znamuškow"],e=["zapisk","zapiska","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Pšosym lašuj "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Pšosym zapódaj nanejmjenjej "+a+" "+u(a,n)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(n){return"Móžoš jano "+n.maximum+" "+u(n.maximum,e)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/el.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(n){var e=n.input.length-n.maximum,u="Παρακαλώ διαγράψτε "+e+" χαρακτήρ";return 1==e&&(u+="α"),1!=e&&(u+="ες"),u},inputTooShort:function(n){return"Παρακαλώ συμπληρώστε "+(n.minimum-n.input.length)+" ή περισσότερους χαρακτήρες"},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(n){var e="Μπορείτε να επιλέξετε μόνο "+n.maximum+" επιλογ";return 1==n.maximum&&(e+="ή"),1!=n.maximum&&(e+="ές"),e},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"},removeAllItems:function(){return"Καταργήστε όλα τα στοιχεία"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/en.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Please delete "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Please enter "+(e.minimum-e.input.length)+" or more characters"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var n="You can only select "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No results found"},searching:function(){return"Searching…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/es.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Por favor, elimine "+n+" car";return r+=1==n?"ácter":"acteres"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Por favor, introduzca "+n+" car";return r+=1==n?"ácter":"acteres"},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var n="Sólo puede seleccionar "+e.maximum+" elemento";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Eliminar todos los elementos"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/et.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var n=e.input.length-e.maximum,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" vähem"},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" rohkem"},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var n="Saad vaid "+e.maximum+" tulemus";return 1==e.maximum?n+="e":n+="t",n+=" valida"},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"},removeAllItems:function(){return"Eemalda kõik esemed"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/eu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gutxiago"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gehiago"},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return 1===e.maximum?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"},removeAllItems:function(){return"Kendu elementu guztiak"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/fa.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(n){return"لطفاً "+(n.input.length-n.maximum)+" کاراکتر را حذف نمایید"},inputTooShort:function(n){return"لطفاً تعداد "+(n.minimum-n.input.length)+" کاراکتر یا بیشتر وارد نمایید"},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(n){return"شما تنها می‌توانید "+n.maximum+" آیتم را انتخاب نمایید"},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."},removeAllItems:function(){return"همه موارد را حذف کنید"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/fi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(n){return"Ole hyvä ja anna "+(n.input.length-n.maximum)+" merkkiä vähemmän"},inputTooShort:function(n){return"Ole hyvä ja anna "+(n.minimum-n.input.length)+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(n){return"Voit valita ainoastaan "+n.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"},removeAllItems:function(){return"Poista kaikki kohteet"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/fr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var n=e.input.length-e.maximum;return"Supprimez "+n+" caractère"+(n>1?"s":"")},inputTooShort:function(e){var n=e.minimum-e.input.length;return"Saisissez au moins "+n+" caractère"+(n>1?"s":"")},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1?"s":"")},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"},removeAllItems:function(){return"Supprimer tous les éléments"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/gl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var n=e.input.length-e.maximum;return 1===n?"Elimine un carácter":"Elimine "+n+" caracteres"},inputTooShort:function(e){var n=e.minimum-e.input.length;return 1===n?"Engada un carácter":"Engada "+n+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return 1===e.maximum?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Elimina todos os elementos"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/he.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="נא למחוק ";return r+=1===e?"תו אחד":e+" תווים"},inputTooShort:function(n){var e=n.minimum-n.input.length,r="נא להכניס ";return r+=1===e?"תו אחד":e+" תווים",r+=" או יותר"},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(n){var e="באפשרותך לבחור עד ";return 1===n.maximum?e+="פריט אחד":e+=n.maximum+" פריטים",e},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"},removeAllItems:function(){return"הסר את כל הפריטים"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/hi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(n){var e=n.input.length-n.maximum,r=e+" अक्षर को हटा दें";return e>1&&(r=e+" अक्षरों को हटा दें "),r},inputTooShort:function(n){return"कृपया "+(n.minimum-n.input.length)+" या अधिक अक्षर दर्ज करें"},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(n){return"आप केवल "+n.maximum+" आइटम का चयन कर सकते हैं"},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."},removeAllItems:function(){return"सभी वस्तुओं को हटा दें"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/hr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hr",[],function(){function n(n){var e=" "+n+" znak";return n%10<5&&n%10>0&&(n%100<5||n%100>19)?n%10>1&&(e+="a"):e+="ova",e}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(e){return"Unesite "+n(e.input.length-e.maximum)},inputTooShort:function(e){return"Unesite još "+n(e.minimum-e.input.length)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(n){return"Maksimalan broj odabranih stavki je "+n.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Ukloni sve stavke"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/hsb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hsb",[],function(){var n=["znamješko","znamješce","znamješka","znamješkow"],e=["zapisk","zapiskaj","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Prošu zhašej "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Prošu zapodaj znajmjeńša "+a+" "+u(a,n)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(n){return"Móžeš jenož "+n.maximum+" "+u(n.maximum,e)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/hu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){return"Túl hosszú. "+(e.input.length-e.maximum)+" karakterrel több, mint kellene."},inputTooShort:function(e){return"Túl rövid. Még "+(e.minimum-e.input.length)+" 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…"},removeAllItems:function(){return"Távolítson el minden elemet"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/hy.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(n){return"Խնդրում ենք հեռացնել "+(n.input.length-n.maximum)+" նշան"},inputTooShort:function(n){return"Խնդրում ենք մուտքագրել "+(n.minimum-n.input.length)+" կամ ավել նշաններ"},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(n){return"Դուք կարող եք ընտրել առավելագույնը "+n.maximum+" կետ"},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"},removeAllItems:function(){return"Հեռացնել բոլոր տարրերը"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/id.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(n){return"Hapuskan "+(n.input.length-n.maximum)+" huruf"},inputTooShort:function(n){return"Masukkan "+(n.minimum-n.input.length)+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(n){return"Anda hanya dapat memilih "+n.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Hapus semua item"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/is.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/is",[],function(){return{inputTooLong:function(n){var t=n.input.length-n.maximum,e="Vinsamlegast styttið texta um "+t+" staf";return t<=1?e:e+"i"},inputTooShort:function(n){var t=n.minimum-n.input.length,e="Vinsamlegast skrifið "+t+" staf";return t>1&&(e+="i"),e+=" í viðbót"},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(n){return"Þú getur aðeins valið "+n.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"},removeAllItems:function(){return"Fjarlægðu öll atriði"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/it.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Per favore cancella "+n+" caratter";return t+=1!==n?"i":"e"},inputTooShort:function(e){return"Per favore inserisci "+(e.minimum-e.input.length)+" o più caratteri"},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var n="Puoi selezionare solo "+e.maximum+" element";return 1!==e.maximum?n+="i":n+="o",n},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"},removeAllItems:function(){return"Rimuovi tutti gli oggetti"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ja.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(n){return n.input.length-n.maximum+" 文字を削除してください"},inputTooShort:function(n){return"少なくとも "+(n.minimum-n.input.length)+" 文字を入力してください"},loadingMore:function(){return"読み込み中…"},maximumSelected:function(n){return n.maximum+" 件しか選択できません"},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"},removeAllItems:function(){return"すべてのアイテムを削除"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ka.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ka",[],function(){return{errorLoading:function(){return"მონაცემების ჩატვირთვა შეუძლებელია."},inputTooLong:function(n){return"გთხოვთ აკრიფეთ "+(n.input.length-n.maximum)+" სიმბოლოთი ნაკლები"},inputTooShort:function(n){return"გთხოვთ აკრიფეთ "+(n.minimum-n.input.length)+" სიმბოლო ან მეტი"},loadingMore:function(){return"მონაცემების ჩატვირთვა…"},maximumSelected:function(n){return"თქვენ შეგიძლიათ აირჩიოთ არაუმეტეს "+n.maximum+" ელემენტი"},noResults:function(){return"რეზულტატი არ მოიძებნა"},searching:function(){return"ძიება…"},removeAllItems:function(){return"ამოიღე ყველა ელემენტი"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/km.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(n){return"សូមលុបចេញ "+(n.input.length-n.maximum)+" អក្សរ"},inputTooShort:function(n){return"សូមបញ្ចូល"+(n.minimum-n.input.length)+" អក្សរ រឺ ច្រើនជាងនេះ"},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(n){return"អ្នកអាចជ្រើសរើសបានតែ "+n.maximum+" ជម្រើសប៉ុណ្ណោះ"},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."},removeAllItems:function(){return"លុបធាតុទាំងអស់"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ko.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(n){return"너무 깁니다. "+(n.input.length-n.maximum)+" 글자 지워주세요."},inputTooShort:function(n){return"너무 짧습니다. "+(n.minimum-n.input.length)+" 글자 더 입력해주세요."},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(n){return"최대 "+n.maximum+"개까지만 선택 가능합니다."},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"},removeAllItems:function(){return"모든 항목 삭제"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/lt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/lt",[],function(){function n(n,e,i,t){return n%10==1&&(n%100<11||n%100>19)?e:n%10>=2&&n%10<=9&&(n%100<11||n%100>19)?i:t}return{inputTooLong:function(e){var i=e.input.length-e.maximum,t="Pašalinkite "+i+" simbol";return t+=n(i,"į","ius","ių")},inputTooShort:function(e){var i=e.minimum-e.input.length,t="Įrašykite dar "+i+" simbol";return t+=n(i,"į","ius","ių")},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(e){var i="Jūs galite pasirinkti tik "+e.maximum+" element";return i+=n(e.maximum,"ą","us","ų")},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"},removeAllItems:function(){return"Pašalinti visus elementus"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/lv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/lv",[],function(){function e(e,n,u,i){return 11===e?n:e%10==1?u:i}return{inputTooLong:function(n){var u=n.input.length-n.maximum,i="Lūdzu ievadiet par "+u;return(i+=" simbol"+e(u,"iem","u","iem"))+" mazāk"},inputTooShort:function(n){var u=n.minimum-n.input.length,i="Lūdzu ievadiet vēl "+u;return i+=" simbol"+e(u,"us","u","us")},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(n){var u="Jūs varat izvēlēties ne vairāk kā "+n.maximum;return u+=" element"+e(n.maximum,"us","u","us")},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"},removeAllItems:function(){return"Noņemt visus vienumus"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/mk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/mk",[],function(){return{inputTooLong:function(n){var e=(n.input.length,n.maximum,"Ве молиме внесете "+n.maximum+" помалку карактер");return 1!==n.maximum&&(e+="и"),e},inputTooShort:function(n){var e=(n.minimum,n.input.length,"Ве молиме внесете уште "+n.maximum+" карактер");return 1!==n.maximum&&(e+="и"),e},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(n){var e="Можете да изберете само "+n.maximum+" ставк";return 1===n.maximum?e+="а":e+="и",e},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"},removeAllItems:function(){return"Отстрани ги сите предмети"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ms.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(n){return"Sila hapuskan "+(n.input.length-n.maximum)+" aksara"},inputTooShort:function(n){return"Sila masukkan "+(n.minimum-n.input.length)+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(n){return"Anda hanya boleh memilih "+n.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Keluarkan semua item"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/nb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){return"Vennligst fjern "+(e.input.length-e.maximum)+" tegn"},inputTooShort:function(e){return"Vennligst skriv inn "+(e.minimum-e.input.length)+" tegn til"},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…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ne.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ne",[],function(){return{errorLoading:function(){return"नतिजाहरु देखाउन सकिएन।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="कृपया "+e+" अक्षर मेटाउनुहोस्।";return 1!=e&&(u+="कृपया "+e+" अक्षरहरु मेटाउनुहोस्।"),u},inputTooShort:function(n){return"कृपया बाँकी रहेका "+(n.minimum-n.input.length)+" वा अरु धेरै अक्षरहरु भर्नुहोस्।"},loadingMore:function(){return"अरु नतिजाहरु भरिँदैछन् …"},maximumSelected:function(n){var e="तँपाई "+n.maximum+" वस्तु मात्र छान्न पाउँनुहुन्छ।";return 1!=n.maximum&&(e="तँपाई "+n.maximum+" वस्तुहरु मात्र छान्न पाउँनुहुन्छ।"),e},noResults:function(){return"कुनै पनि नतिजा भेटिएन।"},searching:function(){return"खोजि हुँदैछ…"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/nl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){return"Gelieve "+(e.input.length-e.maximum)+" karakters te verwijderen"},inputTooShort:function(e){return"Gelieve "+(e.minimum-e.input.length)+" of meer karakters in te voeren"},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var n=1==e.maximum?"kan":"kunnen",r="Er "+n+" maar "+e.maximum+" item";return 1!=e.maximum&&(r+="s"),r+=" worden geselecteerd"},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"},removeAllItems:function(){return"Verwijder alle items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/pl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/pl",[],function(){var n=["znak","znaki","znaków"],e=["element","elementy","elementów"],r=function(n,e){return 1===n?e[0]:n>1&&n<=4?e[1]:n>=5?e[2]:void 0};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Usuń "+t+" "+r(t,n)},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Podaj przynajmniej "+t+" "+r(t,n)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(n){return"Możesz zaznaczyć tylko "+n.maximum+" "+r(n.maximum,e)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"},removeAllItems:function(){return"Usuń wszystkie przedmioty"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ps.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="د مهربانۍ لمخي "+e+" توری ړنګ کړئ";return 1!=e&&(r=r.replace("توری","توري")),r},inputTooShort:function(n){return"لږ تر لږه "+(n.minimum-n.input.length)+" يا ډېر توري وليکئ"},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(n){var e="تاسو يوازي "+n.maximum+" قلم په نښه کولای سی";return 1!=n.maximum&&(e=e.replace("قلم","قلمونه")),e},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."},removeAllItems:function(){return"ټول توکي لرې کړئ"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/pt-BR.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Apague "+n+" caracter";return 1!=n&&(r+="es"),r},inputTooShort:function(e){return"Digite "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var n="Você só pode selecionar "+e.maximum+" ite";return 1==e.maximum?n+="m":n+="ns",n},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/pt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var r=e.input.length-e.maximum,n="Por favor apague "+r+" ";return n+=1!=r?"caracteres":"caractere"},inputTooShort:function(e){return"Introduza "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var r="Apenas pode seleccionar "+e.maximum+" ";return r+=1!=e.maximum?"itens":"item"},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ro.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;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 1!==t&&(n+="e"),n},inputTooShort:function(e){return"Vă rugăm să introduceți "+(e.minimum-e.input.length)+" sau mai multe caractere"},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",1!==e.maximum&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"},removeAllItems:function(){return"Eliminați toate elementele"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/ru.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ru",[],function(){function n(n,e,r,u){return n%10<5&&n%10>0&&n%100<5||n%100>20?n%10>1?r:e:u}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Пожалуйста, введите на "+r+" символ";return u+=n(r,"","a","ов"),u+=" меньше"},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Пожалуйста, введите ещё хотя бы "+r+" символ";return u+=n(r,"","a","ов")},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(e){var r="Вы можете выбрать не более "+e.maximum+" элемент";return r+=n(e.maximum,"","a","ов")},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"},removeAllItems:function(){return"Удалить все элементы"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadajte o jeden znak menej":t>=2&&t<=4?"Prosím, zadajte o "+e[t](!0)+" znaky menej":"Prosím, zadajte o "+t+" znakov menej"},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadajte ešte jeden znak":t<=4?"Prosím, zadajte ešte ďalšie "+e[t](!0)+" znaky":"Prosím, zadajte ešte ďalších "+t+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(n){return 1==n.maximum?"Môžete zvoliť len jednu položku":n.maximum>=2&&n.maximum<=4?"Môžete zvoliť najviac "+e[n.maximum](!1)+" položky":"Môžete zvoliť najviac "+n.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"},removeAllItems:function(){return"Odstráňte všetky položky"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Prosim zbrišite "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Prosim vpišite še "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var n="Označite lahko največ "+e.maximum+" predmet";return 2==e.maximum?n+="a":1!=e.maximum&&(n+="e"),n},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"},removeAllItems:function(){return"Odstranite vse elemente"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sq.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/sq",[],function(){return{errorLoading:function(){return"Rezultatet nuk mund të ngarkoheshin."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Të lutem fshi "+n+" karakter";return 1!=n&&(t+="e"),t},inputTooShort:function(e){return"Të lutem shkruaj "+(e.minimum-e.input.length)+" ose më shumë karaktere"},loadingMore:function(){return"Duke ngarkuar më shumë rezultate…"},maximumSelected:function(e){var n="Mund të zgjedhësh vetëm "+e.maximum+" element";return 1!=e.maximum&&(n+="e"),n},noResults:function(){return"Nuk u gjet asnjë rezultat"},searching:function(){return"Duke kërkuar…"},removeAllItems:function(){return"Hiq të gjitha sendet"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sr-Cyrl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr-Cyrl",[],function(){function n(n,e,r,u){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:u}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Обришите "+r+" симбол";return u+=n(r,"","а","а")},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Укуцајте бар још "+r+" симбол";return u+=n(r,"","а","а")},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(e){var r="Можете изабрати само "+e.maximum+" ставк";return r+=n(e.maximum,"у","е","и")},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr",[],function(){function n(n,e,r,t){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(e){var r=e.input.length-e.maximum,t="Obrišite "+r+" simbol";return t+=n(r,"","a","a")},inputTooShort:function(e){var r=e.minimum-e.input.length,t="Ukucajte bar još "+r+" simbol";return t+=n(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(e){var r="Možete izabrati samo "+e.maximum+" stavk";return r+=n(e.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/sv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(n){return"Vänligen sudda ut "+(n.input.length-n.maximum)+" tecken"},inputTooShort:function(n){return"Vänligen skriv in "+(n.minimum-n.input.length)+" eller fler tecken"},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(n){return"Du kan max välja "+n.maximum+" element"},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"},removeAllItems:function(){return"Ta bort alla objekt"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/th.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(n){return"โปรดลบออก "+(n.input.length-n.maximum)+" ตัวอักษร"},inputTooShort:function(n){return"โปรดพิมพ์เพิ่มอีก "+(n.minimum-n.input.length)+" ตัวอักษร"},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(n){return"คุณสามารถเลือกได้ไม่เกิน "+n.maximum+" รายการ"},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"},removeAllItems:function(){return"ลบรายการทั้งหมด"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/tk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | 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;e.define("select2/i18n/tk",[],function(){return{errorLoading:function(){return"Netije ýüklenmedi."},inputTooLong:function(e){return e.input.length-e.maximum+" harp bozuň."},inputTooShort:function(e){return"Ýene-de iň az "+(e.minimum-e.input.length)+" harp ýazyň."},loadingMore:function(){return"Köpräk netije görkezilýär…"},maximumSelected:function(e){return"Diňe "+e.maximum+" sanysyny saýlaň."},noResults:function(){return"Netije tapylmady."},searching:function(){return"Gözlenýär…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/tr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(n){return n.input.length-n.maximum+" karakter daha girmelisiniz"},inputTooShort:function(n){return"En az "+(n.minimum-n.input.length)+" karakter daha girmelisiniz"},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(n){return"Sadece "+n.maximum+" seçim yapabilirsiniz"},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"},removeAllItems:function(){return"Tüm öğeleri kaldır"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/uk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/uk",[],function(){function n(n,e,u,r){return n%100>10&&n%100<15?r:n%10==1?e:n%10>1&&n%10<5?u:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(e){return"Будь ласка, видаліть "+(e.input.length-e.maximum)+" "+n(e.maximum,"літеру","літери","літер")},inputTooShort:function(n){return"Будь ласка, введіть "+(n.minimum-n.input.length)+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(e){return"Ви можете вибрати лише "+e.maximum+" "+n(e.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"},removeAllItems:function(){return"Видалити всі елементи"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/vi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/vi",[],function(){return{inputTooLong:function(n){return"Vui lòng xóa bớt "+(n.input.length-n.maximum)+" ký tự"},inputTooShort:function(n){return"Vui lòng nhập thêm từ "+(n.minimum-n.input.length)+" ký tự trở lên"},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(n){return"Chỉ có thể chọn được "+n.maximum+" lựa chọn"},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"},removeAllItems:function(){return"Xóa tất cả các mục"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/zh-CN.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/select2/i18n/zh-TW.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(n){return"請刪掉"+(n.input.length-n.maximum)+"個字元"},inputTooShort:function(n){return"請再輸入"+(n.minimum-n.input.length)+"個字元"},loadingMore:function(){return"載入中…"},maximumSelected:function(n){return"你只能選擇最多"+n.maximum+"項"},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"},removeAllItems:function(){return"刪除所有項目"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /staticfiles/admin/js/vendor/xregexp/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007-2017 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 | -------------------------------------------------------------------------------- /staticfiles/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.ecc2cd5e.css", 4 | "main.js": "/static/js/main.3ff53e20.js", 5 | "static/js/787.5ad96c4b.chunk.js": "/static/js/787.5ad96c4b.chunk.js", 6 | "index.html": "/index.html", 7 | "main.ecc2cd5e.css.map": "/static/css/main.ecc2cd5e.css.map", 8 | "main.3ff53e20.js.map": "/static/js/main.3ff53e20.js.map", 9 | "787.5ad96c4b.chunk.js.map": "/static/js/787.5ad96c4b.chunk.js.map" 10 | }, 11 | "entrypoints": [ 12 | "static/css/main.ecc2cd5e.css", 13 | "static/js/main.3ff53e20.js" 14 | ] 15 | } -------------------------------------------------------------------------------- /staticfiles/build/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/build/favicon-16x16.png -------------------------------------------------------------------------------- /staticfiles/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/build/favicon.ico -------------------------------------------------------------------------------- /staticfiles/build/index.html: -------------------------------------------------------------------------------- 1 | Capture The Moment
-------------------------------------------------------------------------------- /staticfiles/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/build/logo192.png -------------------------------------------------------------------------------- /staticfiles/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/build/logo512.png -------------------------------------------------------------------------------- /staticfiles/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /staticfiles/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /staticfiles/build/static/js/main.3ff53e20.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! ***************************************************************************** 14 | Copyright (c) Microsoft Corporation. All rights reserved. 15 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 16 | this file except in compliance with the License. You may obtain a copy of the 17 | License at http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 21 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 22 | MERCHANTABLITY OR NON-INFRINGEMENT. 23 | 24 | See the Apache Version 2.0 License for specific language governing permissions 25 | and limitations under the License. 26 | ***************************************************************************** */ 27 | 28 | /** @license React v0.20.2 29 | * scheduler.production.min.js 30 | * 31 | * Copyright (c) Facebook, Inc. and its affiliates. 32 | * 33 | * This source code is licensed under the MIT license found in the 34 | * LICENSE file in the root directory of this source tree. 35 | */ 36 | 37 | /** @license React v16.13.1 38 | * react-is.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v17.0.2 47 | * react-dom.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v17.0.2 56 | * react-jsx-runtime.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | /** @license React v17.0.2 65 | * react.production.min.js 66 | * 67 | * Copyright (c) Facebook, Inc. and its affiliates. 68 | * 69 | * This source code is licensed under the MIT license found in the 70 | * LICENSE file in the root directory of this source tree. 71 | */ 72 | -------------------------------------------------------------------------------- /staticfiles/cloudinary/js/canvas-to-blob.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";var e=t.HTMLCanvasElement&&t.HTMLCanvasElement.prototype,o=t.Blob&&function(){try{return Boolean(new Blob)}catch(t){return!1}}(),n=o&&t.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(t){return!1}}(),r=t.BlobBuilder||t.WebKitBlobBuilder||t.MozBlobBuilder||t.MSBlobBuilder,a=/^data:((.*?)(;charset=.*?)?)(;base64)?,/,i=(o||r)&&t.atob&&t.ArrayBuffer&&t.Uint8Array&&function(t){var e,i,l,u,c,f,b,d,B;if(!(e=t.match(a)))throw new Error("invalid data URI");for(i=e[2]?e[1]:"text/plain"+(e[3]||";charset=US-ASCII"),l=!!e[4],u=t.slice(e[0].length),c=l?atob(u):decodeURIComponent(u),f=new ArrayBuffer(c.length),b=new Uint8Array(f),d=0;d= 980px wide, so add padding to the body to prevent 2 | content running up underneath it. */ 3 | 4 | h1 { 5 | font-weight: 300; 6 | } 7 | 8 | h2, h3 { 9 | font-weight: 300; 10 | } 11 | 12 | .resource-description, .response-info { 13 | margin-bottom: 2em; 14 | } 15 | 16 | .version:before { 17 | content: "v"; 18 | opacity: 0.6; 19 | padding-right: 0.25em; 20 | } 21 | 22 | .version { 23 | font-size: 70%; 24 | } 25 | 26 | .format-option { 27 | font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace; 28 | } 29 | 30 | .button-form { 31 | float: right; 32 | margin-right: 1em; 33 | } 34 | 35 | td.nested { 36 | padding: 0 !important; 37 | } 38 | 39 | td.nested > table { 40 | margin: 0; 41 | } 42 | 43 | form select, form input:not([type=checkbox]), form textarea { 44 | width: 90%; 45 | } 46 | 47 | form select[multiple] { 48 | height: 150px; 49 | } 50 | 51 | /* To allow tooltips to work on disabled elements */ 52 | .disabled-tooltip-shield { 53 | position: absolute; 54 | top: 0; 55 | right: 0; 56 | bottom: 0; 57 | left: 0; 58 | } 59 | 60 | .errorlist { 61 | margin-top: 0.5em; 62 | } 63 | 64 | pre { 65 | overflow: auto; 66 | word-wrap: normal; 67 | white-space: pre; 68 | font-size: 12px; 69 | } 70 | 71 | .page-header { 72 | border-bottom: none; 73 | padding-bottom: 0px; 74 | } 75 | 76 | #filtersModal form input[type=submit] { 77 | width: auto; 78 | } 79 | 80 | #filtersModal .modal-body h2 { 81 | margin-top: 0 82 | } 83 | -------------------------------------------------------------------------------- /staticfiles/rest_framework/css/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #D14; } 6 | .kwd, .prettyprint .tag { color: #1e347b; } 7 | .typ, .atn, .dec, .var { color: teal; } 8 | .pln { color: #48484c; } 9 | 10 | .prettyprint { 11 | padding: 8px; 12 | background-color: #f7f7f9; 13 | border: 1px solid #e1e1e8; 14 | } 15 | .prettyprint.linenums { 16 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 17 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 18 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 19 | } 20 | 21 | /* Specify class=linenums on a pre to get line numbering */ 22 | ol.linenums { 23 | margin: 0 0 0 33px; /* IE indents via margin-left */ 24 | } 25 | ol.linenums li { 26 | padding-left: 12px; 27 | color: #bebec5; 28 | line-height: 20px; 29 | text-shadow: 0 1px 0 #fff; 30 | } -------------------------------------------------------------------------------- /staticfiles/rest_framework/docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | This is the GitHub theme for highlight.js 3 | 4 | github.com style (c) Vasily Polovnyov 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | color: #333; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rule .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body, 79 | .hljs-name { 80 | color: #008080; 81 | } 82 | 83 | .hljs-regexp { 84 | color: #009926; 85 | } 86 | 87 | .hljs-symbol, 88 | .ruby .hljs-symbol .hljs-string, 89 | .lisp .hljs-keyword, 90 | .clojure .hljs-keyword, 91 | .scheme .hljs-keyword, 92 | .tex .hljs-special, 93 | .hljs-prompt { 94 | color: #990073; 95 | } 96 | 97 | .hljs-built_in { 98 | color: #0086b3; 99 | } 100 | 101 | .hljs-preprocessor, 102 | .hljs-pragma, 103 | .hljs-pi, 104 | .hljs-doctype, 105 | .hljs-shebang, 106 | .hljs-cdata { 107 | color: #999; 108 | font-weight: bold; 109 | } 110 | 111 | .hljs-deletion { 112 | background: #fdd; 113 | } 114 | 115 | .hljs-addition { 116 | background: #dfd; 117 | } 118 | 119 | .diff .hljs-change { 120 | background: #0086b3; 121 | } 122 | 123 | .hljs-chunk { 124 | color: #aaa; 125 | } 126 | -------------------------------------------------------------------------------- /staticfiles/rest_framework/docs/css/jquery.json-view.min.css: -------------------------------------------------------------------------------- 1 | .json-view{position:relative} 2 | .json-view .collapser{width:20px;height:18px;display:block;position:absolute;left:-1.7em;top:-.2em;z-index:5;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpiYGBgOADE%2F3Hgw0DM4IRHgSsDFOzFInmMAQnY49ONzZRjDFiADT7dMLALiE8y4AGW6LoBAgwAuIkf%2F%2FB7O9sAAAAASUVORK5CYII%3D);background-repeat:no-repeat;background-position:center center;opacity:.5;cursor:pointer} 3 | .json-view .collapsed{-ms-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-khtml-transform:rotate(-90deg);-webkit-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)} 4 | .json-view .bl{display:block;padding-left:20px;margin-left:-20px;position:relative} 5 | .json-view{font-family:monospace} 6 | .json-view ul{list-style-type:none;padding-left:2em;border-left:1px dotted;margin:.3em} 7 | .json-view ul li{position:relative} 8 | .json-view .comments,.json-view .dots{display:none;-moz-user-select:none;-ms-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none} 9 | .json-view .comments{padding-left:.8em;font-style:italic;color:#888} 10 | .json-view .bool,.json-view .null,.json-view .num,.json-view .undef{font-weight:700;color:#1A01CC} 11 | .json-view .str{color:#800} -------------------------------------------------------------------------------- /staticfiles/rest_framework/docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/rest_framework/docs/img/favicon.ico -------------------------------------------------------------------------------- /staticfiles/rest_framework/docs/img/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-0412/API_Django/2fe694997713372b791572a78e86e2460706f303/staticfiles/rest_framework/docs/img/grid.png -------------------------------------------------------------------------------- /staticfiles/rest_framework/docs/js/jquery.json-view.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.json-view - jQuery collapsible JSON plugin 3 | * @version v1.0.0 4 | * @link http://github.com/bazh/jquery.json-view 5 | * @license MIT 6 | */ 7 | !function(e){"use strict";var n=function(n){var a=e("",{"class":"collapser",on:{click:function(){var n=e(this);n.toggleClass("collapsed");var a=n.parent().children(".block"),p=a.children("ul");n.hasClass("collapsed")?(p.hide(),a.children(".dots, .comments").show()):(p.show(),a.children(".dots, .comments").hide())}}});return n&&a.addClass("collapsed"),a},a=function(a,p){var t=e.extend({},{nl2br:!0},p),r=function(e){return e.toString()?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""},s=function(n,a){return e("",{"class":a,html:r(n)})},l=function(a,p){switch(e.type(a)){case"object":p||(p=0);var c=e("",{"class":"block"}),d=Object.keys(a).length;if(!d)return c.append(s("{","b")).append(" ").append(s("}","b"));c.append(s("{","b"));var i=e("