├── users ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── views.py └── forms.py ├── profiles ├── __init__.py ├── migrations │ └── __init__.py ├── tests.py ├── apps.py ├── admin.py ├── utils.py ├── tokens.py ├── urls.py ├── models.py ├── forms.py └── views.py ├── static ├── css │ ├── style.css │ ├── style.min.css │ └── addons │ │ ├── datatables.min.css │ │ └── datatables.css ├── img │ ├── overlays │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.png │ │ ├── 07.png │ │ ├── 08.png │ │ └── 09.png │ ├── lightbox │ │ ├── preloader.gif │ │ ├── default-skin.png │ │ └── default-skin.svg │ └── svg │ │ ├── arrow_left.svg │ │ └── arrow_right.svg ├── font │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.woff │ │ ├── Roboto-Thin.woff2 │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.woff │ │ └── Roboto-Regular.woff2 ├── index.html └── js │ ├── popper.min.js │ ├── bootstrap.min.js │ └── addons │ └── datatables.min.js ├── website ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── compose.yaml ├── readme_images ├── home.PNG ├── signin.PNG ├── signup.PNG └── dashboard.PNG ├── templates ├── app │ ├── home.html │ ├── profile_edit.html │ ├── dashboard.html │ └── signup.html ├── registration │ ├── account_activation_email.html │ ├── logout.html │ ├── account_activation_sent.html │ ├── account_activation_invalid.html │ └── login.html ├── base.html └── navbar.html ├── requirements.txt ├── Dockerfile ├── manage.py ├── .gitignore └── README.md /users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /profiles/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/css/style.min.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /profiles/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /profiles/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | build: website 4 | ports: 5 | - '8000:8000' 6 | -------------------------------------------------------------------------------- /readme_images/home.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/readme_images/home.PNG -------------------------------------------------------------------------------- /readme_images/signin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/readme_images/signin.PNG -------------------------------------------------------------------------------- /readme_images/signup.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/readme_images/signup.PNG -------------------------------------------------------------------------------- /readme_images/dashboard.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/readme_images/dashboard.PNG -------------------------------------------------------------------------------- /static/img/overlays/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/01.png -------------------------------------------------------------------------------- /static/img/overlays/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/02.png -------------------------------------------------------------------------------- /static/img/overlays/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/03.png -------------------------------------------------------------------------------- /static/img/overlays/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/04.png -------------------------------------------------------------------------------- /static/img/overlays/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/05.png -------------------------------------------------------------------------------- /static/img/overlays/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/06.png -------------------------------------------------------------------------------- /static/img/overlays/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/07.png -------------------------------------------------------------------------------- /static/img/overlays/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/08.png -------------------------------------------------------------------------------- /static/img/overlays/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/overlays/09.png -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /profiles/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProfilesConfig(AppConfig): 5 | name = 'profiles' 6 | -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /static/img/lightbox/preloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/lightbox/preloader.gif -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /static/img/lightbox/default-skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/img/lightbox/default-skin.png -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /static/font/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexmhack/Django-Signup/HEAD/static/font/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /templates/app/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Home 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 | {{ block.super }} 10 | 11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /static/img/svg/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/img/svg/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /profiles/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Profile 4 | 5 | @admin.register(Profile) 6 | class ProfileModelAdmin(admin.ModelAdmin): 7 | list_display = ('user', 'location') 8 | list_search_fields = ('user', 'location') 9 | -------------------------------------------------------------------------------- /templates/registration/account_activation_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | Hi {{ user.username }}, 3 | 4 | Please click on the link below to confirm your registration: 5 | 6 | http://{{ domain }}{% url 'profiles:activate' uidb64=uid token=token %} 7 | {% endautoescape %} 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | certifi==2024.2.2 3 | charset-normalizer==3.3.2 4 | Django==5.0.3 5 | django-utils-six==2.0 6 | django-widget-tweaks==1.5.0 7 | idna==3.6 8 | python-decouple==3.8 9 | requests==2.31.0 10 | six==1.16.0 11 | sqlparse==0.4.4 12 | typing_extensions==4.10.0 13 | urllib3==2.2.1 14 | -------------------------------------------------------------------------------- /templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Logout 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 |

You are successfully logged out. You can login again.

10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /templates/registration/account_activation_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | {{ block.super }} - Check Your Email Account 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 |

Check your email account for verifying your django account.

10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import home_view, signup_view, dashboard_view 4 | 5 | app_name = "users" 6 | 7 | urlpatterns = [ 8 | path('', home_view, name='home'), 9 | path('signup/', signup_view, name='sign-up'), 10 | path('dashboard/', dashboard_view, name='dashboard'), 11 | ] 12 | -------------------------------------------------------------------------------- /templates/registration/account_activation_invalid.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | {{ block.super }} - Check Your Email Account 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 |

The account activation link you are using is either expired or is no longer valid.

10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /profiles/utils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | def get_location_from_ip(ip_address): 4 | try: 5 | response = requests.get(f"http://ip-api.com/json/{ip_address}").json() 6 | city = response.get("city") 7 | country_code = response.get("countryCode") 8 | return f"{city} - {country_code}" 9 | except Exception as e: 10 | print(e) 11 | return "Failed fetching location" 12 | -------------------------------------------------------------------------------- /profiles/tokens.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | import six 3 | 4 | class AccountActivationTokenGenerator(PasswordResetTokenGenerator): 5 | def _make_hash_value(self, user, timestamp): 6 | return ( 7 | six.text_type(user.pk) + six.text_type(timestamp) + 8 | six.text_type(user.profile.email_confirmed) 9 | ) 10 | 11 | 12 | account_activation_token = AccountActivationTokenGenerator() 13 | -------------------------------------------------------------------------------- /website/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for website project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/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", "website.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | FROM python:3.11 3 | EXPOSE 8000 4 | 5 | ENV PROJECT_KEY=$PROJECT_KEY 6 | 7 | # set working directory 8 | WORKDIR /Django-Signup 9 | COPY requirements.txt /Django-Signup 10 | 11 | # install app dependencies 12 | RUN pip3 install -r requirements.txt --no-cache-dir 13 | COPY . /Django-Signup 14 | ENTRYPOINT ["python3"] 15 | 16 | RUN python manage.py makemigrations 17 | RUN python manage.py migrate 18 | CMD ["manage.py", "runserver", "0.0.0.0:8000"] 19 | -------------------------------------------------------------------------------- /website/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.contrib.auth.views import LoginView, LogoutView 4 | 5 | urlpatterns = [ 6 | path('admin/', admin.site.urls), 7 | path('', include('users.urls', namespace='users')), 8 | path('profile/', include('profiles.urls', namespace='profiles')), 9 | ] 10 | 11 | urlpatterns += [ 12 | path('login/', LoginView.as_view(), name='login'), 13 | path('logout/', LogoutView.as_view(template_name='registration/logout.html'), name='logout'), 14 | ] 15 | -------------------------------------------------------------------------------- /profiles/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, re_path 2 | 3 | from .views import ( 4 | signup, ProfileUpdateView, account_activation_sent_view, account_activate 5 | ) 6 | 7 | app_name = 'profiles' 8 | 9 | urlpatterns = [ 10 | path('', signup, name='profile-signup'), 11 | path('/edit/', ProfileUpdateView.as_view(), name='profile-edit'), 12 | ] 13 | 14 | urlpatterns += [ 15 | path('account-activation-sent/', account_activation_sent_view, name='account-activation-sent'), 16 | path('activate///', 17 | account_activate, name='activate'), 18 | ] 19 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /profiles/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.db.models.signals import post_save 4 | from django.dispatch import receiver 5 | 6 | class Profile(models.Model): 7 | user = models.OneToOneField(User, on_delete=models.CASCADE) 8 | bio = models.CharField(max_length=50, blank=True) 9 | location = models.CharField(max_length=30) 10 | email_confirmed = models.BooleanField(default=False) 11 | 12 | 13 | @receiver(post_save, sender=User) 14 | def update_user_profile(sender, instance, created, **kwargs): 15 | if created: 16 | Profile.objects.create(user=instance) 17 | instance.profile.save() 18 | -------------------------------------------------------------------------------- /templates/app/profile_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block title %} 5 | Edit Bio 6 | {% endblock %} 7 | 8 | {% block content %} 9 | 10 |
11 | {% for field in form %} 12 |

13 | {{ field.label_tag }} 14 | {{ field|attr:"rows:20"|attr:"cols:20" }} 15 | 18 | {% for error in field.errors %} 19 |

{{ error }}

20 | {% endfor %} 21 |

22 | {% endfor %} 23 |
24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /templates/app/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Dashboard 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% if messages %} 10 | 11 | {% for message in messages %} 12 | 13 | 14 |
15 |
16 |

{{ message }}

17 |
18 |
19 | 20 | 21 | {% endfor %} 22 | 23 | {% endif %} 24 | 25 |

Welcome {{ request.user }}

26 |

Bio: {{ user.profile.bio }}

27 |

Joined: {{ user.profile.timestamp }}

28 | 29 | {% endblock content %} 30 | -------------------------------------------------------------------------------- /users/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib.auth import authenticate, login 3 | from django.contrib.auth.forms import UserCreationForm 4 | from django.contrib import messages 5 | from django.contrib.auth.decorators import login_required 6 | 7 | from .forms import SignUpForm 8 | 9 | def signup_view(request): 10 | if request.user.is_authenticated: 11 | return redirect('users:dashboard') 12 | if request.method == "POST": 13 | form = SignUpForm(request.POST) 14 | if form.is_valid(): 15 | form.save() 16 | username = form.cleaned_data.get('username') 17 | password = form.cleaned_data.get('password1') 18 | user = authenticate(username=username, password=password) 19 | login(request, user) 20 | return redirect('users:dashboard') 21 | else: 22 | messages.error(request, 'Correct the errors below') 23 | else: 24 | form = SignUpForm() 25 | 26 | return render(request, 'app/signup.html', {'form': form}) 27 | 28 | 29 | @login_required 30 | def dashboard_view(request): 31 | return render(request, 'app/dashboard.html') 32 | 33 | 34 | def home_view(request): 35 | return render(request, 'app/home.html') 36 | -------------------------------------------------------------------------------- /users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | 5 | class SignUpForm(UserCreationForm): 6 | email = forms.EmailField( 7 | label='', 8 | max_length=254, 9 | widget=forms.EmailInput( 10 | attrs={ 11 | "placeholder": "Email", 12 | "class": "form-control" 13 | } 14 | ) 15 | ) 16 | 17 | username = forms.CharField( 18 | label='', 19 | max_length=30, 20 | min_length=5, 21 | required=True, 22 | widget=forms.TextInput( 23 | attrs={ 24 | "placeholder": "Username", 25 | "class": "form-control" 26 | } 27 | ) 28 | ) 29 | 30 | password1 = forms.CharField( 31 | label='', 32 | max_length=30, 33 | min_length=8, 34 | required=True, 35 | widget=forms.PasswordInput( 36 | attrs={ 37 | "placeholder": "Password", 38 | "class": "form-control" 39 | } 40 | ) 41 | ) 42 | 43 | password2 = forms.CharField( 44 | label='', 45 | max_length=30, 46 | min_length=8, 47 | required=True, 48 | widget=forms.PasswordInput( 49 | attrs={ 50 | "placeholder": "Confirm Password", 51 | "class": "form-control" 52 | } 53 | ) 54 | ) 55 | 56 | class Meta: 57 | model = User 58 | fields = ('username', 'email', 'password1', 'password2') 59 | -------------------------------------------------------------------------------- /profiles/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | 5 | class SignUpForm(UserCreationForm): 6 | username = forms.CharField( 7 | label='', 8 | max_length=30, 9 | min_length=5, 10 | required=True, 11 | widget=forms.TextInput( 12 | attrs={ 13 | "placeholder": "Username", 14 | "class": "form-control" 15 | } 16 | ) 17 | ) 18 | 19 | email = forms.EmailField( 20 | label='', 21 | max_length=255, 22 | required=True, 23 | widget=forms.EmailInput( 24 | attrs={ 25 | "placeholder": "Email", 26 | "class": "form-control" 27 | } 28 | ) 29 | ) 30 | 31 | password1 = forms.CharField( 32 | label='', 33 | max_length=30, 34 | min_length=8, 35 | required=True, 36 | widget=forms.PasswordInput( 37 | attrs={ 38 | "placeholder": "Password", 39 | "class": "form-control" 40 | } 41 | ) 42 | ) 43 | 44 | password2 = forms.CharField( 45 | label='', 46 | max_length=30, 47 | min_length=8, 48 | required=True, 49 | widget=forms.PasswordInput( 50 | attrs={ 51 | "placeholder": "Confirm Password", 52 | "class": "form-control" 53 | } 54 | ) 55 | ) 56 | 57 | location = forms.CharField( 58 | label='', 59 | max_length=30, 60 | required=False, 61 | widget=forms.HiddenInput( 62 | attrs={ 63 | "display": "none" 64 | } 65 | ) 66 | ) 67 | 68 | class Meta: 69 | model = User 70 | fields = ('username', 'email', 'password1', 'password2', 'location') 71 | -------------------------------------------------------------------------------- /static/img/lightbox/default-skin.svg: -------------------------------------------------------------------------------- 1 | default-skin 2 -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Material Design Bootstrap 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

Material Design for Bootstrap

26 | 27 |
Thank you for using our product. We're glad you're with us.
28 | 29 |

MDB Team

30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # migrations folder 107 | profiles/migrations 108 | -------------------------------------------------------------------------------- /static/css/addons/datatables.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable thead{cursor:pointer}table.dataTable thead>tr>td:active,table.dataTable thead>tr>th:active{outline:0}div.dataTables_wrapper div.dataTables_length.d-flex.flex-row label{margin-top:1.2rem;margin-right:1rem}div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select .select-dropdown,div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select span{margin-top:1rem}div.dataTables_wrapper div.dataTables_filter label,div.dataTables_wrapper div.dataTables_length label{text-align:left;font-weight:400;padding-top:.5rem;padding-bottom:.5rem}div.dataTables_wrapper div.dataTables_filter input,div.dataTables_wrapper div.dataTables_filter select,div.dataTables_wrapper div.dataTables_length input,div.dataTables_wrapper div.dataTables_length select{width:auto}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter input{margin-left:.5rem;display:inline-block}div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{font-weight:400;padding-top:1rem;padding-bottom:1rem}div.dataTables_wrapper div.dataTables_paginate{text-align:right;margin:0}div.dataTables_wrapper div.dataTables_paginate ul.pagination{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.active .page-link:focus{background-color:#4285f4}div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item .page-link:focus{-webkit-box-shadow:none;box-shadow:none}@media (max-width:767px){div.dataTables_wrapper div .dataTables_filter,div.dataTables_wrapper div .dataTables_info,div.dataTables_wrapper div .dataTables_length,div.dataTables_wrapper div .dataTables_paginate ul.pagination{text-align:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}}.bs-select select{display:inline-block!important} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Django App | {% block title %}{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% if not form %} 24 | {% include "navbar.html" %} 25 | {% endif %} 26 | 27 | 28 |
29 | 30 | {% block content %} 31 | 32 |
33 |

Material Design for Bootstrap

34 | 35 |
Thank you for using our product. We're glad you're with us.
36 | 37 |

MDB Team

38 |
39 | 40 | {% endblock %} 41 |
42 | 43 | 44 | 45 | 46 | 47 | {% block script %} 48 | {% endblock %} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /templates/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | 63 | -------------------------------------------------------------------------------- /profiles/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth import login, authenticate 3 | from django.views.generic import UpdateView 4 | from django.shortcuts import render, redirect 5 | from django.contrib.auth.mixins import LoginRequiredMixin 6 | from django.contrib.sites.shortcuts import get_current_site 7 | from django.template.loader import render_to_string 8 | from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode 9 | from django.utils.encoding import force_bytes 10 | from django.contrib.auth import get_user_model 11 | 12 | from .forms import SignUpForm 13 | from .models import Profile 14 | from .utils import get_location_from_ip 15 | from .tokens import account_activation_token 16 | 17 | User = get_user_model() 18 | 19 | def signup(request): 20 | if request.method == 'POST': 21 | form = SignUpForm(request.POST) 22 | if form.is_valid(): 23 | user = form.save(commit=False) 24 | user.is_active = False 25 | user.save() 26 | user = form.save() 27 | user.refresh_from_db() # load the profile instance created by the signal 28 | user.profile.location = form.cleaned_data.get('location') 29 | ip_address = user.profile.location 30 | location = get_location_from_ip(ip_address) 31 | print(location) 32 | user.profile.location = location 33 | user.save() 34 | current_site = get_current_site(request) 35 | subject = "Activate your Django Serives Account" 36 | message = render_to_string('registration/account_activation_email.html', { 37 | 'user': user, 38 | 'domain': current_site.domain, 39 | 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), 40 | 'token': account_activation_token.make_token(user) 41 | }) 42 | user.email_user(subject, message) 43 | return redirect('profiles:account-activation-sent') 44 | else: 45 | form = SignUpForm() 46 | return render(request, 'app/signup.html', { 47 | 'form': form, 48 | 'profile': True 49 | }) 50 | 51 | 52 | def account_activation_sent_view(request): 53 | return render(request, 'registration/account_activation_sent.html') 54 | 55 | 56 | def account_activate(request, uidb64, token): 57 | try: 58 | uid = urlsafe_base64_decode(uidb64).decode() 59 | print(uid) 60 | user = User.objects.get(pk=uid) 61 | except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: 62 | print(e) 63 | user = None 64 | 65 | if user is not None and account_activation_token.check_token(user, token): 66 | user.is_active = True 67 | user.profile.email_confirmed = True 68 | user.save() 69 | login(request, user) 70 | return redirect('users:dashboard') 71 | else: 72 | return render(request, 'registration/account_activation_invalid.html') 73 | 74 | 75 | class ProfileUpdateView(LoginRequiredMixin, UpdateView): 76 | model = Profile 77 | template_name = "app/profile_edit.html" 78 | fields = ('bio',) 79 | -------------------------------------------------------------------------------- /templates/app/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Sign Up 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% if messages %} 10 | 11 | {% for message in messages %} 12 | 13 | 14 |
15 |
16 |

{{ message }}

17 |
18 |
19 | 20 | 21 | {% endfor %} 22 | 23 | {% endif %} 24 | 25 |
26 |
27 |
28 | 29 | 43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | {% csrf_token %} 52 |

Sign up

53 | 54 | 55 | 56 | {% for field in form %} 57 |

58 | {{ field.label_tag }} 59 | {{ field }} 60 | 63 | {% for error in field.errors %} 64 |

{{ error }}

65 | {% endfor %} 66 |

67 | {% endfor %} 68 | 69 | 70 | 71 | 72 | 73 |

or sign up with:

74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | 90 | 91 |

By clicking 92 | Sign up you agree to our 93 | terms of service 94 |

95 |
96 | 97 |
98 | 99 |
100 | 101 |
102 |
103 | 104 | {% block script %} 105 | 110 | 111 | 112 | {% endblock %} 113 | 114 | {% endblock %} 115 | -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Sign in 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% if messages %} 10 | 11 | {% for message in messages %} 12 | 13 | 14 |
15 |
16 |

{{ message }}

17 |
18 |
19 | 20 | 21 | {% endfor %} 22 | 23 | {% endif %} 24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | Home 32 | Sign up 33 | 34 | Login 35 | 36 | Profile Sign Up 37 | Dashboard 38 | Profile 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 47 |
48 | 49 |

Sign in

50 | 51 | {% csrf_token %} 52 | 53 |

54 |

55 |

56 |

57 |

58 |

60 |

61 |

62 | {% if form.errors %} 63 |

64 | Your username and password didn't match. 65 | Please try again. 66 |

67 | {% endif %} 68 | 69 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |

Not a member? 88 | Register 89 |

90 | 91 | 92 |

or sign in with:

93 | 94 |
95 | 96 |
97 | 98 |
99 | 100 |
101 |
102 | 103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /website/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for website project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | from decouple import config 16 | 17 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = os.environ.get("PROJECT_KEY", False) or config("PROJECT_KEY") 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [] 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | 'widget_tweaks', 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'users', 44 | 'profiles', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'website.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'website.wsgi.application' 76 | 77 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | } 88 | } 89 | 90 | 91 | # Password validation 92 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 93 | 94 | AUTH_PASSWORD_VALIDATORS = [ 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 106 | }, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 112 | 113 | LANGUAGE_CODE = 'en-us' 114 | 115 | TIME_ZONE = 'UTC' 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | 124 | # Static files (CSS, JavaScript, Images) 125 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 126 | 127 | STATIC_URL = '/static/' 128 | 129 | STATICFILES_DIRS = [ 130 | os.path.join(BASE_DIR, 'static') 131 | ] 132 | 133 | 134 | LOGIN_URL = 'login' 135 | LOGOUT_URL = 'logout' 136 | LOGIN_REDIRECT_URL = 'users:dashboard' 137 | LOGOUT_REDIRECT_URL = 'users:home' 138 | 139 | 140 | # Email backend 141 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 142 | -------------------------------------------------------------------------------- /static/css/addons/datatables.css: -------------------------------------------------------------------------------- 1 | /* 2 | * MDBootstrap integration with Datatables 3 | * Learn more: https://mdbootstrap.com/content/bootstrap-datatables/ 4 | * About MDBootstrap: https://mdbootstrap.com/ 5 | * 6 | * This combined file was created by the DataTables downloader builder: 7 | * https://datatables.net/download 8 | * 9 | * To rebuild or modify this file with the latest versions of the included 10 | * software please visit: 11 | * https://datatables.net/download/#bs4/dt-1.10.18 12 | * 13 | * Included libraries: 14 | * DataTables 1.10.18 15 | */ 16 | 17 | table.dataTable thead { 18 | cursor: pointer; 19 | } 20 | 21 | table.dataTable thead>tr>th:active { 22 | outline: none; 23 | } 24 | 25 | table.dataTable thead>tr>td:active { 26 | outline: none; 27 | } 28 | 29 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row label { 30 | margin-top: 1.2rem; 31 | margin-right: 1rem; 32 | } 33 | 34 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select span, 35 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select .select-dropdown { 36 | margin-top: 1rem; 37 | } 38 | 39 | div.dataTables_wrapper div.dataTables_length label, 40 | div.dataTables_wrapper div.dataTables_filter label { 41 | text-align: left; 42 | font-weight: normal; 43 | padding-top: .5rem; 44 | padding-bottom: .5rem; 45 | } 46 | 47 | div.dataTables_wrapper div.dataTables_length select, 48 | div.dataTables_wrapper div.dataTables_length input, 49 | div.dataTables_wrapper div.dataTables_filter select, 50 | div.dataTables_wrapper div.dataTables_filter input { 51 | width: auto; 52 | } 53 | 54 | div.dataTables_wrapper div.dataTables_filter { 55 | text-align: right; 56 | } 57 | 58 | div.dataTables_wrapper div.dataTables_filter input { 59 | margin-left: .5rem; 60 | display: inline-block; 61 | } 62 | 63 | div.dataTables_wrapper div.dataTables_info, 64 | div.dataTables_wrapper div.dataTables_paginate { 65 | font-weight: normal; 66 | padding-top: 1rem; 67 | padding-bottom: 1rem; 68 | } 69 | 70 | div.dataTables_wrapper div.dataTables_paginate { 71 | text-align: right; 72 | margin: 0; 73 | } 74 | 75 | div.dataTables_wrapper div.dataTables_paginate ul.pagination { 76 | -webkit-box-pack: end; 77 | -webkit-justify-content: flex-end; 78 | -ms-flex-pack: end; 79 | justify-content: flex-end; 80 | } 81 | 82 | div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.active .page-link:focus { 83 | background-color: #4285f4; 84 | } 85 | 86 | div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item .page-link:focus { 87 | -webkit-box-shadow: none; 88 | box-shadow: none; 89 | } 90 | 91 | @media (max-width: 767px) { 92 | div.dataTables_wrapper div .dataTables_length, 93 | div.dataTables_wrapper div .dataTables_filter, 94 | div.dataTables_wrapper div .dataTables_info, 95 | div.dataTables_wrapper div .dataTables_paginate ul.pagination { 96 | text-align: center; 97 | -webkit-box-pack: center; 98 | -webkit-justify-content: center; 99 | -ms-flex-pack: center; 100 | justify-content: center; 101 | } 102 | } 103 | 104 | .bs-select select { 105 | display: inline-block !important; 106 | } 107 | 108 | table.dataTable thead { 109 | cursor: pointer; 110 | } 111 | 112 | table.dataTable thead>tr>th:active { 113 | outline: none; 114 | } 115 | 116 | table.dataTable thead>tr>td:active { 117 | outline: none; 118 | } 119 | 120 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row label { 121 | margin-top: 1.2rem; 122 | margin-right: 1rem; 123 | } 124 | 125 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select span, 126 | div.dataTables_wrapper div.dataTables_length.d-flex.flex-row .select-wrapper.mdb-select .select-dropdown { 127 | margin-top: 1rem; 128 | } 129 | 130 | div.dataTables_wrapper div.dataTables_length label, 131 | div.dataTables_wrapper div.dataTables_filter label { 132 | text-align: left; 133 | font-weight: normal; 134 | padding-top: .5rem; 135 | padding-bottom: .5rem; 136 | } 137 | 138 | div.dataTables_wrapper div.dataTables_length select, 139 | div.dataTables_wrapper div.dataTables_length input, 140 | div.dataTables_wrapper div.dataTables_filter select, 141 | div.dataTables_wrapper div.dataTables_filter input { 142 | width: auto; 143 | } 144 | 145 | div.dataTables_wrapper div.dataTables_filter { 146 | text-align: right; 147 | } 148 | 149 | div.dataTables_wrapper div.dataTables_filter input { 150 | margin-left: .5rem; 151 | display: inline-block; 152 | } 153 | 154 | div.dataTables_wrapper div.dataTables_info, 155 | div.dataTables_wrapper div.dataTables_paginate { 156 | font-weight: normal; 157 | padding-top: 1rem; 158 | padding-bottom: 1rem; 159 | } 160 | 161 | div.dataTables_wrapper div.dataTables_paginate { 162 | text-align: right; 163 | margin: 0; 164 | } 165 | 166 | div.dataTables_wrapper div.dataTables_paginate ul.pagination { 167 | -webkit-box-pack: end; 168 | -webkit-justify-content: flex-end; 169 | -ms-flex-pack: end; 170 | justify-content: flex-end; 171 | } 172 | 173 | div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.active .page-link:focus { 174 | background-color: #4285f4; 175 | } 176 | 177 | div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item .page-link:focus { 178 | -webkit-box-shadow: none; 179 | box-shadow: none; 180 | } 181 | 182 | @media (max-width: 767px) { 183 | div.dataTables_wrapper div .dataTables_length, 184 | div.dataTables_wrapper div .dataTables_filter, 185 | div.dataTables_wrapper div .dataTables_info, 186 | div.dataTables_wrapper div .dataTables_paginate ul.pagination { 187 | text-align: center; 188 | -webkit-box-pack: center; 189 | -webkit-justify-content: center; 190 | -ms-flex-pack: center; 191 | justify-content: center; 192 | } 193 | } 194 | 195 | .bs-select select { 196 | display: inline-block !important; 197 | } 198 | -------------------------------------------------------------------------------- /static/js/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2017 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=window.getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e||-1!==['HTML','BODY','#document'].indexOf(e.nodeName))return window.document.body;var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll)/.test(r+s+p)?e:n(o(e))}function r(e){var o=e&&e.offsetParent,i=o&&o.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(o.nodeName)&&'static'===t(o,'position')?r(o):o:window.document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return window.document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,i=o?e:t,n=o?t:e,a=document.createRange();a.setStart(i,0),a.setEnd(n,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||i.contains(n))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1=o.clientWidth&&i>=o.clientHeight}),l=0i[e]&&!t.escapeWithReference&&(n=V(p[o],i[e]-('right'===e?p.width:p.height))),se({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=de({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=_,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var n=e.placement.split('-')[0],r=e.offsets,p=r.popper,s=r.reference,d=-1!==['left','right'].indexOf(n),a=d?'height':'width',l=d?'Top':'Left',f=l.toLowerCase(),m=d?'left':'top',c=d?'bottom':'right',g=O(i)[a];s[c]-gp[c]&&(e.offsets.popper[f]+=s[f]+g-p[c]);var u=s[f]+s[a]/2-g/2,b=t(e.instance.popper,'margin'+l).replace('px',''),y=u-h(e.offsets.popper)[f]-b;return y=X(V(p[a]-g,y),0),e.arrowElement=i,e.offsets.arrow={},e.offsets.arrow[f]=Math.round(y),e.offsets.arrow[m]='',e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=w(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=L(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case fe.FLIP:p=[i,n];break;case fe.CLOCKWISE:p=K(i);break;case fe.COUNTERCLOCKWISE:p=K(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=L(i);var a=e.offsets.popper,l=e.offsets.reference,f=_,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)f(l.top)||'bottom'===i&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===i&&c||'right'===i&&h||'top'===i&&g||'bottom'===i&&u,y=-1!==['top','bottom'].indexOf(i),w=!!t.flipVariations&&(y&&'start'===r&&c||y&&'end'===r&&h||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),w&&(r=j(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=de({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=N(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=L(t),e.offsets.popper=h(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightSign up 122 |
123 | {% csrf_token %} 124 | {% for field in form %} 125 |

126 | {{ field.label_tag }}
127 | {{ field }} 128 | {% if field.help_text %} 129 | {{ field.help_text }} 130 | {% endif %} 131 | {% for error in field.errors %} 132 |

{{ error }}

133 | {% endfor %} 134 |

135 | {% endfor %} 136 | 137 |
138 | {% endblock %} 139 | ``` 140 | 141 | This is a little different way of rendering form. There are more ways like 142 | 143 | ``` 144 | {{ form.as_p }} 145 | {{ form.as_table }} 146 | {{ form.as_ul }} 147 | ``` 148 | 149 | # Sign Up Form With Extra Fields 150 | So far we have been using the default fields that ```UserCreationForm``` provides 151 | us. But what if we wanted the email address of the new user which is the 152 | important part aside from the username and password. 153 | 154 | For that we can inherit a new form class from ```UserCreationForm```. Create a 155 | new file named ```forms.py``` in the **website** folder. All of this should go 156 | in a separate app so you can start another app. Let's just do that as our project 157 | is increasing in size. 158 | 159 | ``` 160 | python manage.py startapp users 161 | ``` 162 | 163 | Include app in project **settings** 164 | 165 | ``` 166 | ... 167 | INSTALLED_APPS = [ 168 | 'django.contrib.admin', 169 | 'django.contrib.auth', 170 | 'django.contrib.contenttypes', 171 | 'django.contrib.sessions', 172 | 'django.contrib.messages', 173 | 'django.contrib.staticfiles', 174 | 'users', 175 | ] 176 | ``` 177 | 178 | Move the file ```website/views.py``` to ```users``` folder and replace it. 179 | These are some of the changes which you would have to do a lot of times if you 180 | are working on a large project in any language. 181 | 182 | Since we moved the ```views.py``` file, all our imports in ```urls.py``` will 183 | give errors. Let's also create a ```urls.py``` file in ```users``` folder. 184 | 185 | From the **website/urls.py** file change this piece of code into 186 | 187 | ``` 188 | from .views import signup_view, dashboard_view, home_view 189 | 190 | urlpatterns = [ 191 | path('admin/', admin.site.urls), 192 | path('signup/', signup_view, name='sign-up'), 193 | path('dashboard/', dashboard_view, name='dashboard'), 194 | path('', home_view, name='home'), 195 | ] 196 | ``` 197 | 198 | replace with below code 199 | 200 | ``` 201 | from django.urls import path, include 202 | 203 | urlpatterns = [ 204 | path('admin/', admin.site.urls), 205 | path('', include('users.urls', namespace='users')), 206 | ] 207 | ``` 208 | 209 | Now we will create a custom user registration form so create a new file ```forms.py``` in **users** folder. 210 | 211 | ``` 212 | from django import forms 213 | from django.contrib.auth.forms import UserCreationForm 214 | from django.contrib.auth.models import User 215 | 216 | class SignUpForm(UserCreationForm): 217 | first_name = forms.CharField( 218 | max_length=10, 219 | min_length=4, 220 | required=True, 221 | widget=forms.TextInput( 222 | attrs={ 223 | "placeholder": "First Name", 224 | "class": "form-control" 225 | } 226 | ) 227 | ) 228 | last_name = forms.CharField( 229 | max_length=30, 230 | required=True, 231 | widget=forms.TextInput( 232 | attrs={ 233 | "placeholder": "Last Name", 234 | "class": "form-control" 235 | } 236 | ) 237 | ) 238 | email = forms.EmailField( 239 | max_length=254, 240 | widget=forms.EmailInput( 241 | attrs={ 242 | "placeholder": "Email", 243 | "class": "form-control" 244 | } 245 | ) 246 | ) 247 | 248 | class Meta: 249 | model = User 250 | fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2',) 251 | ``` 252 | 253 | You can do the same with ```password``` fields by using ```PasswordInput()``` 254 | 255 | ``` 256 | ... 257 | password1 = forms.CharField( 258 | label='', 259 | max_length=30, 260 | min_length=8, 261 | required=True, 262 | widget=forms.PasswordInput( 263 | attrs={ 264 | "placeholder": "Password", 265 | "class": "form-control" 266 | } 267 | ) 268 | ) 269 | 270 | password2 = forms.CharField( 271 | label='', 272 | max_length=30, 273 | min_length=8, 274 | required=True, 275 | widget=forms.PasswordInput( 276 | attrs={ 277 | "placeholder": "Confirm Password", 278 | "class": "form-control" 279 | } 280 | ) 281 | ) 282 | ``` 283 | 284 | This would give the forms a nice look as well as placeholders and the rest of 285 | the django password validation remains intact and active. 286 | 287 | # Sign Up With Profile Model 288 | So far we have been using the ```User``` model from 289 | ```django.contrib.auth.models``` that meets almost all needs but **Django** docs 290 | itself recommends using a custom model for users instead of the ```User``` so in 291 | this section we will be making our own custom model for users and name it 292 | ```Profile``` 293 | 294 | For this part we will start another app name ```profiles``` 295 | 296 | ``` 297 | python manage.py startapp profiles 298 | ``` 299 | 300 | Add profiles app in **settings** 301 | 302 | Inside ```profiles/models.py``` add 303 | 304 | ``` 305 | from django.db import models 306 | from django.contrib.auth.models import User 307 | from django.db.models.signals import post_save 308 | from django.dispatch import receiver 309 | ``` 310 | 311 | In this particular case, the profile is created using a Signal. It’s not 312 | mandatory, but usually it is a good way to implement it. 313 | 314 | ``` 315 | class Profile(models.Model): 316 | user = models.OneToOneField(User, on_delete=models.CASCADE) 317 | bio = models.CharField(max_length=50, blank=True) 318 | location = models.CharField(max_length=30, blank=True) 319 | 320 | 321 | @receiver(post_save, sender=User) 322 | def update_user_profile(sender, instance, created, **kwargs): 323 | if created: 324 | Profile.objects.create(user=instance) 325 | instance.profile.save() 326 | ``` 327 | 328 | This is our custom model, ofcourse you can go far more further adding in birth 329 | date, and profile image and lots more stuff, but for simplicity we are just using 330 | three fields. 331 | 332 | Now we need to create a form so ```forms.py``` file should have 333 | 334 | ``` 335 | from django import forms 336 | from django.contrib.auth.forms import UserCreationForm 337 | from django.contrib.auth.models import User 338 | 339 | class SignUpForm(UserCreationForm): 340 | birth_date = forms.DateField(help_text='Required. Format: YYYY-MM-DD') 341 | 342 | class Meta: 343 | model = User 344 | fields = ('username', 'birth_date', 'password1', 'password2', ) 345 | ``` 346 | 347 | You can add in attributes in the fields again like we did earlier. 348 | 349 | There are a few changes that ```views.py``` file should have 350 | 351 | ``` 352 | from django.contrib.auth import login, authenticate 353 | from django.shortcuts import render, redirect 354 | from mysite.core.forms import SignUpForm 355 | 356 | def signup(request): 357 | if request.method == 'POST': 358 | form = SignUpForm(request.POST) 359 | if form.is_valid(): 360 | user = form.save() 361 | user.refresh_from_db() # load the profile instance created by the signal 362 | user.profile.birth_date = form.cleaned_data.get('birth_date') 363 | user.save() 364 | raw_password = form.cleaned_data.get('password1') 365 | user = authenticate(username=user.username, password=raw_password) 366 | login(request, user) 367 | return redirect('home') 368 | else: 369 | form = SignUpForm() 370 | return render(request, 'signup.html', {'form': form}) 371 | ``` 372 | 373 | Because of the Signal handling the Profile creation, we have a synchronism issue 374 | here. It is easily solved by calling the user.refresh_from_db() method. This 375 | will cause a hard refresh from the database, which will retrieve the profile 376 | instance. 377 | 378 | If you don’t call user.refresh_from_db(), when you try to access the 379 | user.profile, it will return None. 380 | 381 | After refreshing it user model, set the cleaned data to the fields that matter, 382 | and save the user model. The user save will trigger the profile save as well, 383 | that’s why you don’t need to call user.profile.save(), instead you call just 384 | user.save(). 385 | 386 | You can display the user details using 387 | 388 | ``` 389 |

Welcome {{ request.user }}

390 |

Bio: {{ user.profile.bio }}

391 |

Location: {{ user.profile.location }}

392 |

Joined: {{ user.profile.timestamp }}

393 | ``` 394 | 395 | **For customizing the forms you can use [django-widget-tweaks](https://pypi.org/project/django-widget-tweaks/)** 396 | 397 | # Signup With Confirmation Email 398 | Django provides built-in system for sending emails. But first of test purposes 399 | we will be using **Console Backend** for emails. 400 | 401 | Add this settings in ```settings.py``` file 402 | 403 | ``` 404 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 405 | ``` 406 | 407 | Now for checking if a user is authenticated we will create a field in the profile 408 | model to determine if the user is confirmed or not. 409 | 410 | **profiles/models.py** 411 | ``` 412 | class Profile(models.Model): 413 | ... 414 | email_confirmed = models.BooleanField(default=False) 415 | ``` 416 | 417 | And for creating a one time link using django we will create a new file ```tokens.py``` 418 | 419 | ``` 420 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 421 | from django.utils import six 422 | 423 | class AccountActivationTokenGenerator(PasswordResetTokenGenerator): 424 | def _make_hash_value(self, user, timestamp): 425 | return ( 426 | six.text_type(user.pk) + six.text_type(timestamp) + 427 | six.text_type(user.profile.email_confirmed) 428 | ) 429 | 430 | 431 | account_activation_token = AccountActivationTokenGenerator() 432 | ``` 433 | 434 | We use the ```pk``` from the user ```timestamp``` and the ```email_confirmed``` field 435 | to create a token. We basically extended the PasswordResetTokenGenerator to create a 436 | unique token generator to confirm email addresses. This make use of your project’s 437 | SECRET_KEY, so it is a pretty safe and reliable method. 438 | 439 | Now we need to define views for account activation as well as account activation sent 440 | view 441 | 442 | ``` 443 | def account_activation_sent_view(request): 444 | return render(request, 'registration/account_activation_sent.html') 445 | 446 | 447 | def account_activate(request, uidb64, token): 448 | try: 449 | uid = urlsafe_base64_decode(uidb64).decode() 450 | print(uid) 451 | user = User.objects.get(pk=uid) 452 | except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: 453 | print(e) 454 | user = None 455 | 456 | if user is not None and account_activation_token.check_token(user, token): 457 | user.is_active = True 458 | user.profile.email_confirmed = True 459 | user.save() 460 | login(request, user) 461 | return redirect('users:dashboard') 462 | else: 463 | return render(request, 'registration/account_activation_invalid.html') 464 | ``` 465 | 466 | ```account_activation_sent_view``` is justfor redirecting users if their account activation url is wrong. The template **registration/account_activation_sent.html** 467 | will be 468 | 469 | ``` 470 | {% extends "base.html" %} 471 | 472 | {% block title %} 473 | {{ block.super }} - Check Your Email Account 474 | {% endblock %} 475 | 476 | {% block content %} 477 | 478 |

Check your email account for verifying your django account.

479 | 480 | {% endblock %} 481 | ``` 482 | 483 | ```account_activate``` function simply fetches the ```uidb64``` and ```token``` 484 | from the url and uses the **.check_token** function from ```AccountActivationTokenGenerator``` class which takes the user and token. 485 | 486 | You can remove the print statements from the code, I use them for testing purposes 487 | while writing my code. 488 | 489 | **profiles/views.py** 490 | ``` 491 | User = get_user_model() 492 | 493 | def signup(request): 494 | if request.method == 'POST': 495 | form = SignUpForm(request.POST) 496 | if form.is_valid(): 497 | user = form.save(commit=False) 498 | user.is_active = False 499 | user.save() 500 | user = form.save() 501 | current_site = get_current_site(request) 502 | subject = "Activate your Django Serives Account" 503 | message = render_to_string('registration/account_activation_email.html', { 504 | 'user': user, 505 | 'domain': current_site.domain, 506 | 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), 507 | 'token': account_activation_token.make_token(user) 508 | }) 509 | user.email_user(subject, message) 510 | return redirect('profiles:account-activation-sent') 511 | else: 512 | form = SignUpForm() 513 | return render(request, 'app/signup.html', { 514 | 'form': form, 515 | 'profile': True 516 | }) 517 | ``` 518 | 519 | This is the code for sending email to the user email, notice we have removed 520 | ```user.refresh_from_db()``` since we are using ```form.save(commit=False)``` 521 | so you can use ```user.refresh_from_db()``` after saving the form 522 | 523 | ``` 524 | ... 525 | if form.is_valid(): 526 | user = form.save(commit=False) 527 | user.is_active = False 528 | user.save() 529 | user = form.save() 530 | user.refresh_from_db() 531 | # your code here 532 | user.save() # call save again 533 | ... 534 | ``` 535 | 536 | This is it. We can extend our model by using the ```smtp``` as email backend that 537 | will actually send email to the email provided by the user but it requires some more 538 | settings to be defined in the **settings.py** file. For more details visit [docs](https://docs.djangoproject.com/en/2.1/topics/email/) 539 | -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,h){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)P(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!(Ie={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Se={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},we="out",Ne={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:(De="show")+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Oe="fade",ke="show",Pe=".tooltip-inner",je=".arrow",He="hover",Le="focus",Re="click",xe="manual",We=function(){function i(t,e){if("undefined"==typeof h)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=pe(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(pe(this.getTipElement()).hasClass(ke))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),pe.removeData(this.element,this.constructor.DATA_KEY),pe(this.element).off(this.constructor.EVENT_KEY),pe(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&pe(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===pe(this.element).css("display"))throw new Error("Please use show on visible elements");var t=pe.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){pe(this.element).trigger(t);var n=pe.contains(this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=Fn.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&pe(i).addClass(Oe);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:pe(document).find(this.config.container);pe(i).data(this.constructor.DATA_KEY,this),pe.contains(this.element.ownerDocument.documentElement,this.tip)||pe(i).appendTo(a),pe(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new h(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:je},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),pe(i).addClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().on("mouseover",null,pe.noop);var l=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,pe(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(pe(this.tip).hasClass(Oe)){var c=Fn.getTransitionDurationFromElement(this.tip);pe(this.tip).one(Fn.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=pe.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),pe(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(pe(this.element).trigger(i),!i.isDefaultPrevented()){if(pe(n).removeClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().off("mouseover",null,pe.noop),this._activeTrigger[Re]=!1,this._activeTrigger[Le]=!1,this._activeTrigger[He]=!1,pe(this.tip).hasClass(Oe)){var o=Fn.getTransitionDurationFromElement(n);pe(n).one(Fn.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){pe(this.getTipElement()).addClass(Te+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||pe(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(pe(t.querySelectorAll(Pe)),this.getTitle()),pe(t).removeClass(Oe+" "+ke)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?pe(e).parent().is(t)||t.empty().append(e):t.text(pe(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getAttachment=function(t){return Ie[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)pe(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==xe){var e=t===He?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===He?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;pe(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}pe(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Le:He]=!0),pe(e.getTipElement()).hasClass(ke)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Le:He]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,pe(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Fn.typeCheckConfig(ve,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=pe(this.getTipElement()),e=t.attr("class").match(be);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(pe(t).removeClass(Oe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=pe(this).data(ye),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),pe(this).data(ye,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return Ae}},{key:"NAME",get:function(){return ve}},{key:"DATA_KEY",get:function(){return ye}},{key:"Event",get:function(){return Ne}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}(),pe.fn[ve]=We._jQueryInterface,pe.fn[ve].Constructor=We,pe.fn[ve].noConflict=function(){return pe.fn[ve]=Ce,We._jQueryInterface},We),Jn=(qe="popover",Ke="."+(Fe="bs.popover"),Me=(Ue=e).fn[qe],Qe="bs-popover",Be=new RegExp("(^|\\s)"+Qe+"\\S+","g"),Ve=l({},zn.Default,{placement:"right",trigger:"click",content:"",template:''}),Ye=l({},zn.DefaultType,{content:"(string|element|function)"}),ze="fade",Ze=".popover-header",Ge=".popover-body",$e={HIDE:"hide"+Ke,HIDDEN:"hidden"+Ke,SHOW:(Je="show")+Ke,SHOWN:"shown"+Ke,INSERTED:"inserted"+Ke,CLICK:"click"+Ke,FOCUSIN:"focusin"+Ke,FOCUSOUT:"focusout"+Ke,MOUSEENTER:"mouseenter"+Ke,MOUSELEAVE:"mouseleave"+Ke},Xe=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){Ue(this.getTipElement()).addClass(Qe+"-"+t)},r.getTipElement=function(){return this.tip=this.tip||Ue(this.config.template)[0],this.tip},r.setContent=function(){var t=Ue(this.getTipElement());this.setElementContent(t.find(Ze),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(Ge),e),t.removeClass(ze+" "+Je)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=Ue(this.getTipElement()),e=t.attr("class").match(Be);null!==e&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||ta;a++)t[a]&&F(n.models.oSearch,t[a])}function j(t){P(t,"orderable","bSortable"),P(t,"orderData","aDataSort"),P(t,"orderSequence","asSorting"),P(t,"orderDataType","sortDataType");var a=t.aDataSort;"number"!=typeof a||e.isArray(a)||(t.aDataSort=[a])}function N(a){if(!n.__browser){var r={};n.__browser=r;var i=e("
").css({position:"fixed",top:0,left:-1*e(t).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(e("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(e("
").css({width:"100%",height:10}))).appendTo("body"),l=i.children(),o=l.children();r.barWidth=l[0].offsetWidth-l[0].clientWidth,r.bScrollOversize=100===o[0].offsetWidth&&100!==l[0].clientWidth,r.bScrollbarLeft=1!==Math.round(o.offset().left),r.bBounding=i[0].getBoundingClientRect().width?!0:!1,i.remove()}e.extend(a.oBrowser,n.__browser),a.oScroll.iBarWidth=n.__browser.barWidth}function H(e,t,a,n,i,l){var s,o=n,u=!1;for(a!==r&&(s=a,u=!0);o!==i;)e.hasOwnProperty(o)&&(s=u?t(s,e[o],o,e):e[o],u=!0,o+=l);return s}function k(t,r){var i=n.defaults.column,l=t.aoColumns.length,o=e.extend({},n.models.oColumn,i,{nTh:r?r:a.createElement("th"),sTitle:i.sTitle?i.sTitle:r?r.innerHTML:"",aDataSort:i.aDataSort?i.aDataSort:[l],mData:i.mData?i.mData:l,idx:l});t.aoColumns.push(o);var s=t.aoPreSearchCols;s[l]=e.extend({},n.models.oSearch,s[l]),O(t,l,e(r).data())}function O(t,a,i){var l=t.aoColumns[a],o=t.oClasses,s=e(l.nTh);if(!l.sWidthOrig){l.sWidthOrig=s.attr("width")||null;var u=(s.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);u&&(l.sWidthOrig=u[1])}i!==r&&null!==i&&(j(i),F(n.defaults.column,i),i.mDataProp===r||i.mData||(i.mData=i.mDataProp),i.sType&&(l._sManualType=i.sType),i.className&&!i.sClass&&(i.sClass=i.className),i.sClass&&s.addClass(i.sClass),e.extend(l,i),ct(l,i,"sWidth","sWidthOrig"),i.iDataSort!==r&&(l.aDataSort=[i.iDataSort]),ct(l,i,"aDataSort"));var f=l.mData,c=ee(f),d=l.mRender?ee(l.mRender):null,h=function(e){return"string"==typeof e&&-1!==e.indexOf("@")};l._bAttrSrc=e.isPlainObject(f)&&(h(f.sort)||h(f.type)||h(f.filter)),l._setter=null,l.fnGetData=function(e,t,a){var n=c(e,t,r,a);return d&&t?d(n,t,e,a):n},l.fnSetData=function(e,t,a){return te(f)(e,t,a)},"number"!=typeof f&&(t._rowReadObject=!0),t.oFeatures.bSort||(l.bSortable=!1,s.addClass(o.sSortableNone));var p=-1!==e.inArray("asc",l.asSorting),v=-1!==e.inArray("desc",l.asSorting);l.bSortable&&(p||v)?p&&!v?(l.sSortingClass=o.sSortableAsc,l.sSortingClassJUI=o.sSortJUIAscAllowed):!p&&v?(l.sSortingClass=o.sSortableDesc,l.sSortingClassJUI=o.sSortJUIDescAllowed):(l.sSortingClass=o.sSortable,l.sSortingClassJUI=o.sSortJUI):(l.sSortingClass=o.sSortableNone,l.sSortingClassJUI="")}function M(e){if(e.oFeatures.bAutoWidth!==!1){var t=e.aoColumns;$e(e);for(var a=0,r=t.length;r>a;a++)t[a].nTh.style.width=t[a].sWidth}var n=e.oScroll;(""!==n.sY||""!==n.sX)&&Je(e),vt(e,null,"column-sizing",[e])}function W(e,t){var a=U(e,"bVisible");return"number"==typeof a[t]?a[t]:null}function B(t,a){var r=U(t,"bVisible"),n=e.inArray(a,r);return-1!==n?n:null}function E(t){var a=0;return e.each(t.aoColumns,function(t,r){r.bVisible&&"none"!==e(r.nTh).css("display")&&a++}),a}function U(t,a){var r=[];return e.map(t.aoColumns,function(e,t){e[a]&&r.push(t)}),r}function V(e){var l,o,s,u,f,c,d,p,v,t=e.aoColumns,a=e.aoData,i=n.ext.type.detect;for(l=0,o=t.length;o>l;l++)if(d=t[l],v=[],!d.sType&&d._sManualType)d.sType=d._sManualType;else if(!d.sType){for(s=0,u=i.length;u>s;s++){for(f=0,c=a.length;c>f&&(v[f]===r&&(v[f]=z(e,f,l,"type")),p=i[s](v[f],e),p||s===i.length-1)&&"html"!==p;f++);if(p){d.sType=p;break}}d.sType||(d.sType="string")}}function X(t,a,n,i){var l,o,s,u,f,c,d,h=t.aoColumns;if(a)for(l=a.length-1;l>=0;l--){d=a[l];var p=d.targets!==r?d.targets:d.aTargets;for(e.isArray(p)||(p=[p]),s=0,u=p.length;u>s;s++)if("number"==typeof p[s]&&p[s]>=0){for(;h.length<=p[s];)k(t);i(p[s],d)}else if("number"==typeof p[s]&&p[s]<0)i(h.length+p[s],d);else if("string"==typeof p[s])for(f=0,c=h.length;c>f;f++)("_all"==p[s]||e(h[f].nTh).hasClass(p[s]))&&i(f,d)}if(n)for(l=0,o=n.length;o>l;l++)i(l,n[l])}function J(t,a,i,l){var o=t.aoData.length,s=e.extend(!0,{},n.models.oRow,{src:i?"dom":"data",idx:o});s._aData=a,t.aoData.push(s);for(var c=t.aoColumns,d=0,h=c.length;h>d;d++)c[d].sType=null;t.aiDisplayMaster.push(o);var p=t.rowIdFn(a);return p!==r&&(t.aIds[p]=s),(i||!t.oFeatures.bDeferRender)&&oe(t,o,i,l),o}function q(t,a){var r;return a instanceof e||(a=e(a)),a.map(function(e,a){return r=le(t,a),J(t,r.data,a,r.cells)})}function G(e,t){return t._DT_RowIndex!==r?t._DT_RowIndex:null}function $(t,a,r){return e.inArray(r,t.aoData[a].anCells)}function z(e,t,a,n){var i=e.iDraw,l=e.aoColumns[a],o=e.aoData[t]._aData,s=l.sDefaultContent,u=l.fnGetData(o,n,{settings:e,row:t,col:a});if(u===r)return e.iDrawError!=i&&null===s&&(ft(e,0,"Requested unknown parameter "+("function"==typeof l.mData?"{function}":"'"+l.mData+"'")+" for row "+t+", column "+a,4),e.iDrawError=i),s;if(u!==o&&null!==u||null===s||n===r){if("function"==typeof u)return u.call(o)}else u=s;return null===u&&"display"==n?"":u}function Y(e,t,a,r){var n=e.aoColumns[a],i=e.aoData[t]._aData;n.fnSetData(i,r,{settings:e,row:t,col:a})}function K(t){return e.map(t.match(/(\\.|[^\.])+/g)||[""],function(e){return e.replace(/\\\./g,".")})}function ee(t){if(e.isPlainObject(t)){var a={};return e.each(t,function(e,t){t&&(a[e]=ee(t))}),function(e,t,n,i){var l=a[t]||a._;return l!==r?l(e,t,n,i):e}}if(null===t)return function(e){return e};if("function"==typeof t)return function(e,a,r,n){return t(e,a,r,n)};if("string"!=typeof t||-1===t.indexOf(".")&&-1===t.indexOf("[")&&-1===t.indexOf("("))return function(e,a){return e[t]};var n=function(t,a,i){var l,o,s,u;if(""!==i)for(var f=K(i),c=0,d=f.length;d>c;c++){if(l=f[c].match(Z),o=f[c].match(Q),l){if(f[c]=f[c].replace(Z,""),""!==f[c]&&(t=t[f[c]]),s=[],f.splice(0,c+1),u=f.join("."),e.isArray(t))for(var h=0,p=t.length;p>h;h++)s.push(n(t[h],a,u));var v=l[0].substring(1,l[0].length-1);t=""===v?s:s.join(v);break}if(o)f[c]=f[c].replace(Q,""),t=t[f[c]]();else{if(null===t||t[f[c]]===r)return r;t=t[f[c]]}}return t};return function(e,a){return n(e,a,t)}}function te(t){if(e.isPlainObject(t))return te(t._);if(null===t)return function(){};if("function"==typeof t)return function(e,a,r){t(e,"set",a,r)};if("string"!=typeof t||-1===t.indexOf(".")&&-1===t.indexOf("[")&&-1===t.indexOf("("))return function(e,a){e[t]=a};var a=function(t,n,i){for(var o,u,f,c,d,l=K(i),s=l[l.length-1],h=0,p=l.length-1;p>h;h++){if(u=l[h].match(Z),f=l[h].match(Q),u){if(l[h]=l[h].replace(Z,""),t[l[h]]=[],o=l.slice(),o.splice(0,h+1),d=o.join("."),e.isArray(n))for(var v=0,g=n.length;g>v;v++)c={},a(c,n[v],d),t[l[h]].push(c);else t[l[h]]=n;return}f&&(l[h]=l[h].replace(Q,""),t=t[l[h]](n)),(null===t[l[h]]||t[l[h]]===r)&&(t[l[h]]={}),t=t[l[h]]}s.match(Q)?t=t[s.replace(Q,"")](n):t[s.replace(Z,"")]=n};return function(e,r){return a(e,r,t)}}function ae(e){return y(e.aoData,"_aData")}function re(e){e.aoData.length=0,e.aiDisplayMaster.length=0,e.aiDisplay.length=0,e.aIds={}}function ne(e,t,a){for(var n=-1,i=0,l=e.length;l>i;i++)e[i]==t?n=i:e[i]>t&&e[i]--;-1!=n&&a===r&&e.splice(n,1)}function ie(e,t,a,n){var l,o,i=e.aoData[t],s=function(a,r){for(;a.childNodes.length;)a.removeChild(a.firstChild);a.innerHTML=z(e,t,r,"display")};if("dom"!==a&&(a&&"auto"!==a||"dom"!==i.src)){var u=i.anCells;if(u)if(n!==r)s(u[n],n);else for(l=0,o=u.length;o>l;l++)s(u[l],l)}else i._aData=le(e,i,n,n===r?r:i._aData).data;i._aSortData=null,i._aFilterData=null;var f=e.aoColumns;if(n!==r)f[n].sType=null;else{for(l=0,o=f.length;o>l;l++)f[l].sType=null;se(e,i)}}function le(t,a,n,i){var s,u,d,l=[],o=a.firstChild,c=0,h=t.aoColumns,p=t._rowReadObject;i=i!==r?i:p?{}:[];var v=function(e,t){if("string"==typeof e){var a=e.indexOf("@");if(-1!==a){var r=e.substring(a+1),n=te(e);n(i,t.getAttribute(r))}}},g=function(t){if(n===r||n===c)if(u=h[c],d=e.trim(t.innerHTML),u&&u._bAttrSrc){var a=te(u.mData._);a(i,d),v(u.mData.sort,t),v(u.mData.type,t),v(u.mData.filter,t)}else p?(u._setter||(u._setter=te(u.mData)),u._setter(i,d)):i[c]=d;c++};if(o)for(;o;)s=o.nodeName.toUpperCase(),("TD"==s||"TH"==s)&&(g(o),l.push(o)),o=o.nextSibling;else{l=a.anCells;for(var b=0,m=l.length;m>b;b++)g(l[b])}var S=a.firstChild?a:a.nTr;if(S){var D=S.getAttribute("id");D&&te(t.rowId)(i,D)}return{data:i,cells:l}}function oe(t,r,n,i){var u,f,c,d,h,l=t.aoData[r],o=l._aData,s=[];if(null===l.nTr){for(u=n||a.createElement("tr"),l.nTr=u,l.anCells=s,u._DT_RowIndex=r,se(t,l),d=0,h=t.aoColumns.length;h>d;d++)c=t.aoColumns[d],f=n?i[d]:a.createElement(c.sCellType),f._DT_CellIndex={row:r,column:d},s.push(f),n&&!c.mRender&&c.mData===d||e.isPlainObject(c.mData)&&c.mData._===d+".display"||(f.innerHTML=z(t,r,d,"display")),c.sClass&&(f.className+=" "+c.sClass),c.bVisible&&!n?u.appendChild(f):!c.bVisible&&n&&f.parentNode.removeChild(f),c.fnCreatedCell&&c.fnCreatedCell.call(t.oInstance,f,z(t,r,d),o,r,d);vt(t,"aoRowCreatedCallback",null,[u,o,r,s])}l.nTr.setAttribute("role","row")}function se(t,a){var r=a.nTr,n=a._aData;if(r){var i=t.rowIdFn(n);if(i&&(r.id=i),n.DT_RowClass){var l=n.DT_RowClass.split(" ");a.__rowc=a.__rowc?I(a.__rowc.concat(l)):l,e(r).removeClass(a.__rowc.join(" ")).addClass(n.DT_RowClass)}n.DT_RowAttr&&e(r).attr(n.DT_RowAttr),n.DT_RowData&&e(r).data(n.DT_RowData)}}function ue(t){var a,r,n,i,l,o=t.nTHead,s=t.nTFoot,u=0===e("th, td",o).length,f=t.oClasses,c=t.aoColumns;for(u&&(i=e("").appendTo(o)),a=0,r=c.length;r>a;a++)l=c[a],n=e(l.nTh).addClass(l.sClass),u&&n.appendTo(i),t.oFeatures.bSort&&(n.addClass(l.sSortingClass),l.bSortable!==!1&&(n.attr("tabindex",t.iTabIndex).attr("aria-controls",t.sTableId),nt(t,l.nTh,a))),l.sTitle!=n[0].innerHTML&&n.html(l.sTitle),bt(t,"header")(t,n,l,f);if(u&&pe(t.aoHeader,o),e(o).find(">tr").attr("role","row"),e(o).find(">tr>th, >tr>td").addClass(f.sHeaderTH),e(s).find(">tr>th, >tr>td").addClass(f.sFooterTH),null!==s){var d=t.aoFooter[0];for(a=0,r=d.length;r>a;a++)l=c[a],l.nTf=d[a].cell,l.sClass&&e(l.nTf).addClass(l.sClass)}}function fe(t,a,n){var i,l,o,s,u,c,d,g,b,h=[],p=[],v=t.aoColumns.length;if(a){for(n===r&&(n=!1),i=0,l=a.length;l>i;i++){for(h[i]=a[i].slice(),h[i].nTr=a[i].nTr,o=v-1;o>=0;o--)t.aoColumns[o].bVisible||n||h[i].splice(o,1);p.push([])}for(i=0,l=h.length;l>i;i++){if(d=h[i].nTr)for(;c=d.firstChild;)d.removeChild(c);for(o=0,s=h[i].length;s>o;o++)if(g=1,b=1,p[i][o]===r){for(d.appendChild(h[i][o].cell),p[i][o]=1;h[i+g]!==r&&h[i][o].cell==h[i+g][o].cell;)p[i+g][o]=1,g++;for(;h[i][o+b]!==r&&h[i][o].cell==h[i][o+b].cell;){for(u=0;g>u;u++)p[i+u][o+b]=1;b++}e(h[i][o].cell).attr("rowspan",g).attr("colspan",b)}}}}function ce(t){var a=vt(t,"aoPreDrawCallback","preDraw",[t]);if(-1!==e.inArray(!1,a))return void Ve(t,!1);var o=[],s=0,u=t.asStripeClasses,f=u.length,d=(t.aoOpenRows.length,t.oLanguage),h=t.iInitDisplayStart,p="ssp"==mt(t),v=t.aiDisplay;t.bDrawing=!0,h!==r&&-1!==h&&(t._iDisplayStart=p?h:h>=t.fnRecordsDisplay()?0:h,t.iInitDisplayStart=-1);var g=t._iDisplayStart,b=t.fnDisplayEnd();if(t.bDeferLoading)t.bDeferLoading=!1,t.iDraw++,Ve(t,!1);else if(p){if(!t.bDestroying&&!be(t))return}else t.iDraw++;if(0!==v.length)for(var m=p?0:g,S=p?t.aoData.length:b,D=m;S>D;D++){var y=v[D],_=t.aoData[y];null===_.nTr&&oe(t,y);var w=_.nTr;if(0!==f){var T=u[s%f];_._sRowStripe!=T&&(e(w).removeClass(_._sRowStripe).addClass(T),_._sRowStripe=T)}vt(t,"aoRowCallback",null,[w,_._aData,s,D,y]),o.push(w),s++}else{var C=d.sZeroRecords;1==t.iDraw&&"ajax"==mt(t)?C=d.sLoadingRecords:d.sEmptyTable&&0===t.fnRecordsTotal()&&(C=d.sEmptyTable),o[0]=e("",{"class":f?u[0]:""}).append(e("",{valign:"top",colSpan:E(t),"class":t.oClasses.sRowEmpty}).html(C))[0]}vt(t,"aoHeaderCallback","header",[e(t.nTHead).children("tr")[0],ae(t),g,b,v]),vt(t,"aoFooterCallback","footer",[e(t.nTFoot).children("tr")[0],ae(t),g,b,v]);var x=e(t.nTBody);x.children().detach(),x.append(e(o)),vt(t,"aoDrawCallback","draw",[t]),t.bSorted=!1,t.bFiltered=!1,t.bDrawing=!1}function de(e,t){var a=e.oFeatures,r=a.bSort,n=a.bFilter;r&&tt(e),n?_e(e,e.oPreviousSearch):e.aiDisplay=e.aiDisplayMaster.slice(),t!==!0&&(e._iDisplayStart=0),e._drawHold=t,ce(e),e._drawHold=!1}function he(t){var a=t.oClasses,r=e(t.nTable),i=e("
").insertBefore(r),l=t.oFeatures,o=e("
",{id:t.sTableId+"_wrapper","class":a.sWrapper+(t.nTFoot?"":" "+a.sNoFooter)});t.nHolding=i[0],t.nTableWrapper=o[0],t.nTableReinsertBefore=t.nTable.nextSibling;for(var u,f,c,d,h,p,s=t.sDom.split(""),v=0;v")[0],d=s[v+1],"'"==d||'"'==d){for(h="",p=2;s[v+p]!=d;)h+=s[v+p],p++;if("H"==h?h=a.sJUIHeader:"F"==h&&(h=a.sJUIFooter),-1!=h.indexOf(".")){var g=h.split(".");c.id=g[0].substr(1,g[0].length-1),c.className=g[1]}else"#"==h.charAt(0)?c.id=h.substr(1,h.length-1):c.className=h;v+=p}o.append(c),o=e(c)}else if(">"==f)o=o.parent();else if("l"==f&&l.bPaginate&&l.bLengthChange)u=We(t);else if("f"==f&&l.bFilter)u=ye(t);else if("r"==f&&l.bProcessing)u=Ue(t);else if("t"==f)u=Xe(t);else if("i"==f&&l.bInfo)u=je(t);else if("p"==f&&l.bPaginate)u=Be(t);else if(0!==n.ext.feature.length)for(var b=n.ext.feature,m=0,S=b.length;S>m;m++)if(f==b[m].cFeature){u=b[m].fnInit(t);break}if(u){var D=t.aanFeatures;D[f]||(D[f]=[]),D[f].push(u),o.append(u)}}i.replaceWith(o),t.nHolding=null}function pe(t,a){var n,i,l,o,s,u,c,d,h,p,v,r=e(a).children("tr"),g=function(e,t,a){for(var r=e[t];r[a];)a++;return a};for(t.splice(0,t.length),l=0,u=r.length;u>l;l++)t.push([]);for(l=0,u=r.length;u>l;l++)for(n=r[l],d=0,i=n.firstChild;i;){if("TD"==i.nodeName.toUpperCase()||"TH"==i.nodeName.toUpperCase())for(h=1*i.getAttribute("colspan"),p=1*i.getAttribute("rowspan"),h=h&&0!==h&&1!==h?h:1,p=p&&0!==p&&1!==p?p:1,c=g(t,l,d),v=1===h?!0:!1,s=0;h>s;s++)for(o=0;p>o;o++)t[l+o][c+s]={cell:i,unique:v},t[l+o].nTr=n;i=i.nextSibling}}function ve(e,t,a){var r=[];a||(a=e.aoHeader,t&&(a=[],pe(a,t)));for(var n=0,i=a.length;i>n;n++)for(var l=0,o=a[n].length;o>l;l++)!a[n][l].unique||r[l]&&e.bSortCellsTop||(r[l]=a[n][l].cell);return r}function ge(t,a,r){if(vt(t,"aoServerParams","serverParams",[a]),a&&e.isArray(a)){var n={},i=/(.*?)\[\]$/;e.each(a,function(e,t){var a=t.name.match(i);if(a){var r=a[0];n[r]||(n[r]=[]),n[r].push(t.value)}else n[t.name]=t.value}),a=n}var l,o=t.ajax,s=t.oInstance,u=function(e){vt(t,null,"xhr",[t,e,t.jqXHR]),r(e)};if(e.isPlainObject(o)&&o.data){l=o.data;var f="function"==typeof l?l(a,t):l;a="function"==typeof l&&f?f:e.extend(!0,a,f),delete o.data}var c={data:a,success:function(e){var a=e.error||e.sError;a&&ft(t,0,a),t.json=e,u(e)},dataType:"json",cache:!1,type:t.sServerMethod,error:function(a,r,n){var i=vt(t,null,"xhr",[t,null,t.jqXHR]);-1===e.inArray(!0,i)&&("parsererror"==r?ft(t,0,"Invalid JSON response",1):4===a.readyState&&ft(t,0,"Ajax error",7)),Ve(t,!1)}};t.oAjaxData=a,vt(t,null,"preXhr",[t,a]),t.fnServerData?t.fnServerData.call(s,t.sAjaxSource,e.map(a,function(e,t){return{name:t,value:e}}),u,t):t.sAjaxSource||"string"==typeof o?t.jqXHR=e.ajax(e.extend(c,{url:o||t.sAjaxSource})):"function"==typeof o?t.jqXHR=o.call(s,a,u,t):(t.jqXHR=e.ajax(e.extend(c,o)),o.data=l)}function be(e){return e.bAjaxDataGet?(e.iDraw++,Ve(e,!0),ge(e,me(e),function(t){Se(e,t)}),!1):!0}function me(t){var s,f,c,d,a=t.aoColumns,r=a.length,i=t.oFeatures,l=t.oPreviousSearch,o=t.aoPreSearchCols,u=[],h=et(t),p=t._iDisplayStart,v=i.bPaginate!==!1?t._iDisplayLength:-1,g=function(e,t){u.push({name:e,value:t})};g("sEcho",t.iDraw),g("iColumns",r),g("sColumns",y(a,"sName").join(",")),g("iDisplayStart",p),g("iDisplayLength",v);var b={draw:t.iDraw,columns:[],order:[],start:p,length:v,search:{value:l.sSearch,regex:l.bRegex}};for(s=0;r>s;s++)c=a[s],d=o[s],f="function"==typeof c.mData?"function":c.mData,b.columns.push({data:f,name:c.sName,searchable:c.bSearchable,orderable:c.bSortable,search:{value:d.sSearch,regex:d.bRegex}}),g("mDataProp_"+s,f),i.bFilter&&(g("sSearch_"+s,d.sSearch),g("bRegex_"+s,d.bRegex),g("bSearchable_"+s,c.bSearchable)),i.bSort&&g("bSortable_"+s,c.bSortable);i.bFilter&&(g("sSearch",l.sSearch),g("bRegex",l.bRegex)),i.bSort&&(e.each(h,function(e,t){b.order.push({column:t.col,dir:t.dir}),g("iSortCol_"+e,t.col),g("sSortDir_"+e,t.dir)}),g("iSortingCols",h.length));var m=n.ext.legacy.ajax;return null===m?t.sAjaxSource?u:b:m?u:b}function Se(e,t){var a=function(e,a){return t[e]!==r?t[e]:t[a]},n=De(e,t),i=a("sEcho","draw"),l=a("iTotalRecords","recordsTotal"),o=a("iTotalDisplayRecords","recordsFiltered");if(i){if(1*is;s++)J(e,n[s]);e.aiDisplay=e.aiDisplayMaster.slice(),e.bAjaxDataGet=!1,ce(e),e._bInitComplete||Oe(e,t),e.bAjaxDataGet=!0,Ve(e,!1)}function De(t,a){var n=e.isPlainObject(t.ajax)&&t.ajax.dataSrc!==r?t.ajax.dataSrc:t.sAjaxDataProp;return"data"===n?a.aaData||a[n]:""!==n?ee(n)(a):a}function ye(t){var r=t.oClasses,n=t.sTableId,i=t.oLanguage,l=t.oPreviousSearch,o=t.aanFeatures,s='',u=i.sSearch;u=u.match(/_INPUT_/)?u.replace("_INPUT_",s):u+s;var f=e("
",{id:o.f?null:n+"_filter","class":r.sFilter}).append(e("