first commit

This commit is contained in:
ayxan committed 2022-08-24 17:37:03 +04:00
commit f9930c2476
53 files changed
+785

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
.venv
__pycache__
migrations
db.sqlite3
src/config/settings/static
.env
View File
Whitespace-only changes.
+4
View File
@@ -0,0 +1,4 @@
SECRET_KEY=
DB_NAME=
DB_USER=
DB_PASSWORD=
+1
View File
@@ -0,0 +1 @@
from .get_request import APIClient
+33
View File
@@ -0,0 +1,33 @@
from requests import get
from django.core.exceptions import ValidationError
class APIClient:
def __init__(self, api_key: str) -> None:
self.api_key = api_key
def __get_data(self, link: str) -> dict:
raw_data = get(link)
if raw_data.status_code == 200:
data = raw_data.json()
if data['errorMessage'] == 'Invalid API Key': raise ValidationError('Invalid API Key')
elif data['errorMessage']: raise ValidationError('id is not correct.')
return data
raise ValidationError('Could not created. Please try again later.')
def key_control(self) -> None:
self.__get_data(f"https://imdb-api.com/en/API/Title/{self.api_key}/tt7939766")
def get_seasons(self, series_id: str) -> list:
data = self.__get_data(f"https://imdb-api.com/en/API/Title/{self.api_key}/{series_id}")
if not data['tvSeriesInfo']:
raise ValidationError('This is not a TV series id')
return data['tvSeriesInfo']['seasons']
def get_episodes_count(self, season: int, series_id: str) -> int:
data = self.__get_data(
f"https://imdb-api.com/en/API/SeasonEpisodes/{self.api_key}/{series_id}/{season}")
return len(data['episodes'])
View File
Whitespace-only changes.
+13
View File
@@ -0,0 +1,13 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
@admin.register(User)
class CustomAdmin(UserAdmin):
list_display = ('username', 'email')
fieldsets = UserAdmin.fieldsets + (
('IMDB API Key Field', {
'fields': ['imdb_api_key']
}),
)
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'account'
+2
View File
@@ -0,0 +1,2 @@
from .register import RegisterForm
from .profile_editing import ProfileEditingForm
+10
View File
@@ -0,0 +1,10 @@
from django.contrib.auth.forms import UserChangeForm
from account.models import User
class ProfileEditingForm(UserChangeForm):
password = None
class Meta:
model = User
fields = ('email', 'username', 'imdb_api_key')
+14
View File
@@ -0,0 +1,14 @@
from django.contrib.auth.forms import UserCreationForm
from account.models import User
class RegisterForm(UserCreationForm):
password = None
class Meta:
model = User
fields = ('username',
'email',
'password1',
'password2',
'imdb_api_key')
+25
View File
@@ -0,0 +1,25 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
from API_client import APIClient
from django.core.exceptions import ValidationError
class User(AbstractUser):
imdb_api_key = models.CharField(max_length=15, blank=False, null=False)
def clean(self) -> None:
client = APIClient(self.imdb_api_key)
client.key_control()
# raw_data = get(f"https://imdb-api.com/en/API/Title/{self.imdb_api_key}/tt0110413")
# if raw_data.status_code == 200: data = raw_data.json()
# else: raise ValidationError('Account not created. Please try again later')
# if not data['errorMessage']: raise ValidationError(data['errorMessage'])
class Meta:
db_table = 'user'
verbose_name = 'User'
verbose_name_plural = 'Users'
def __str__(self):
return self.username
@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %} Change Password {% endblock title %}
{% block content %}
<form enctype="multipart/form-data" method="POST">
{% csrf_token %}
{{form.media}}
{{form|crispy}}
<style>
.btn:hover {
background: #fff;
color: #0069D9;
}
</style>
<input type="submit" value="Change Password" class="btn btn-primary mt-3 mb-5">
</form>
{% endblock content %}
+24
View File
@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %} Login {% endblock title %}
{% block content %}
{% include 'components/message.html' %}
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<style>
.btn:hover {
background: #fff;
color: #0069D9;
}
</style>
<input type="submit" value="Login" class="btn btn-primary mt-3 mb-3">
<p class="mb-5">No account? <a href="{% url 'register' %}">Signup</a></p>
</form>
{% endblock content %}
+24
View File
@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% load static %}
{% block title %} Profile {% endblock title %}
{% block content %}
<div class="card mb-3">
<div class="card-body">
<h5>Username: <small>{{profile.username}}</small></h5>
<h5>Email: <small>{{profile.email|default:""}}</small></h5>
<h5>IMDB API Key: <small>{{profile.imdb_api_key}}</small></h5>
</div>
</div>
<a href="/account/profile-editing" style="color: inherit; text-decoration: none;">
<button type="button" class="btn btn-primary">Edit Profile</button>
</a>
<a href="/account/change-password" style="color: inherit; text-decoration: none;">
<button type="button" class="btn btn-primary">Change Password</button>
</a>
{% endblock content %}
@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %} Profil Güncəllə {% endblock title %}
{% block content %}
<form enctype="multipart/form-data" method="POST">
{% csrf_token %}
{{form.media}}
{{form|crispy}}
<style>
.btn:hover {
background: #fff;
color: #0069D9;
}
</style>
<input type="submit" value="Update Profile" class="btn btn-primary mt-3 mb-5">
</form>
{% endblock content %}
+25
View File
@@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %} Signup {% endblock title %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{form.media}}
{{form|crispy}}
<a href="https://imdb-api.com/Identity/Account/Register">Get API Key</a><br>
<style>
.btn:hover {
background: #fff;
color: #0069D9;
}
</style>
<input type="submit" value="Signup" class="btn btn-primary mt-3 mb-3">
<p class="mb-5">Already have an account? <a href="{% url 'login' %}">Login</a></p>
</form>
{% endblock content %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+15
View File
@@ -0,0 +1,15 @@
from django.urls import path
from account import views
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login', auth_views.LoginView.as_view(
template_name = 'login.html'
), name='login'),
path('register', views.register_view, name='register'),
path('logout', views.logout_view, name='logout'),
path('change-password', views.change_password_view, name='change-password'),
path('profile-editing', views.profile_editing_view, name='profile-editing'),
path('profile', views.ProfileDetailView.as_view(), name='profile')
]
+5
View File
@@ -0,0 +1,5 @@
from .register import register_view
from .logout import logout_view
from .change_password import change_password_view
from .profile_editing import profile_editing_view
from .profile_detail import ProfileDetailView
+22
View File
@@ -0,0 +1,22 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.contrib import messages
@login_required(login_url='/')
def change_password_view(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
messages.success(request, 'Password changed')
return redirect('homepage')
else:
form = PasswordChangeForm(request.user)
return render(request, 'change_password.html', context={'form': form})
+12
View File
@@ -0,0 +1,12 @@
from django.contrib.auth import logout
from django.shortcuts import redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@login_required(login_url='/account/login')
def logout_view(request):
logout(request)
messages.success(request, 'Logged Out')
return redirect('login')
+16
View File
@@ -0,0 +1,16 @@
from django.views.generic import DetailView
from account.models import User
from django.urls import reverse, reverse_lazy
from django.shortcuts import get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
class ProfileDetailView(LoginRequiredMixin, DetailView):
login_url = reverse_lazy('login')
template_name = 'profile_detail.html'
context_object_name = 'profile'
def get_object(self):
return get_object_or_404(
User, id=self.request.user.id
)
+20
View File
@@ -0,0 +1,20 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from account.forms import ProfileEditingForm
@login_required(login_url='/')
def profile_editing_view(request):
if request.method == 'POST':
form = ProfileEditingForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, 'Profile Updated')
return redirect('homepage')
else:
form = ProfileEditingForm(instance=request.user)
return render(request, 'profile_editing.html', context={"form": form})
+35
View File
@@ -0,0 +1,35 @@
from django.shortcuts import render, redirect
from django.contrib import messages
from account.forms import RegisterForm
from django.contrib.auth import login, authenticate
from django.core.exceptions import ValidationError
from requests import get
def register_view(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
# raw_data = get(f"https://imdb-api.com/en/API/Title/{request.POST['imdb_api_key']}/tt0110413")
# if raw_data.status_code == 200: data = raw_data.json()
# else:
# messages.info(request, 'Account not created. Please try again later')
# return redirect('register')
# if data['errorMessage'] == 'Invalid API Key':
# messages.info(request, 'Invalid API Key')
# return redirect('register')
form.save()
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=password)
login(request, user)
messages.success(request, 'Registration Successful')
return redirect('homepage')
else:
form = RegisterForm()
return render(request, 'register.html', context={"form": form})
View File
Whitespace-only changes.
+16
View File
@@ -0,0 +1,16 @@
"""
ASGI config for config project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_asgi_application()
View File
Whitespace-only changes.
+84
View File
@@ -0,0 +1,84 @@
import os
import environ
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = env("SECRET_KEY")
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'account',
'series',
'crispy_forms',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
CRISPY_TEMPLATE_PACK = 'bootstrap4'
LOGIN_REDIRECT_URL = '/'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
AUTH_USER_MODEL = 'account.User'
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+21
View File
@@ -0,0 +1,21 @@
from .base import *
DEBUG = True
ALLOWED_HOSTS = []
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
STATIC_URL = '/static/'
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_ROOT = os.path.join(PROJECT_DIR, 'static')
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
+20
View File
@@ -0,0 +1,20 @@
from .base import *
DEBUG = True
ALLOWED_HOSTS = []
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': 'databasepostgresql',
'PORT': '5432',
}
}
STATIC_URL = '/django_static/'
STATIC_ROOT = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'opt/services/djangoapp/django_static')
+9
View File
@@ -0,0 +1,9 @@
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('account.urls')),
path('', include('series.urls')),
]
+16
View File
@@ -0,0 +1,16 @@
"""
WSGI config for config project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_wsgi_application()
Executable
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
View File
Whitespace-only changes.
+5
View File
@@ -0,0 +1,5 @@
from django.contrib import admin
from .models import SeriesModel
admin.site.register(SeriesModel)
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SeriesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'series'
+42
View File
@@ -0,0 +1,42 @@
from django.db import models
from django.core.exceptions import ValidationError
from API_client import APIClient
from django.core.validators import (MaxValueValidator,
MinValueValidator,
MinLengthValidator)
class SeriesModel(models.Model):
user = models.ForeignKey('account.User', on_delete=models.CASCADE, related_name='series')
title = models.CharField(max_length=35, blank=False, null=False)
imdb_id = models.CharField(max_length=10, validators=[MinLengthValidator(9)],
blank=False, null=False)
last_season = models.IntegerField(validators=[
MaxValueValidator(30),
MinValueValidator(1)
], blank=False, null=False)
last_episode = models.IntegerField(validators=[
MaxValueValidator(60),
MinValueValidator(1)
], blank=False, null=False)
show = models.BooleanField(default=True)
def clean(self) -> None:
if len(self.imdb_id) < 9:
raise ValidationError(f'Ensure this value has at least 9 characters (it has {len(self.imdb_id)}).')
if len(self.imdb_id) > 10:
raise ValidationError(f'Make sure this value is no more than 10 characters (it has {len(self.imdb_id)}).')
client = APIClient(self.user.imdb_api_key)
seasons = client.get_seasons(self.imdb_id)
if str(self.last_season) not in seasons:
raise ValidationError('The season number you entered is not correct.')
episodes_count = client.get_episodes_count(self.last_season, self.imdb_id)
if self.last_episode > episodes_count:
raise ValidationError('The episode number you entered is not correct.')
def __str__(self) -> str:
return f"{self.user.username} - {self.title}"
+9
View File
@@ -0,0 +1,9 @@
{% extends 'base.html' %}
{% block title %} Home Page {% endblock title %}
{% block content %}
{% include 'components/message.html' %}
{% endblock content %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+8
View File
@@ -0,0 +1,8 @@
from django.urls import path
from series import views
urlpatterns = [
path('', views.homepage_view, name='homepage'),
# path('series/create', views.homepage_view, name='homepage'),
]
+1
View File
@@ -0,0 +1 @@
from .homepage import homepage_view
+7
View File
@@ -0,0 +1,7 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
@login_required(login_url='/account/login')
def homepage_view(request):
return render(request, 'homepage.html')
+7
View File
@@ -0,0 +1,7 @@
@charset "UTF-8";/*!
* Bootstrap v5.0.2 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:30Line truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+35
View File
@@ -0,0 +1,35 @@
footer {
position: fixed;
bottom: 0;
width: 100%;
height: 65px;
background: black;
text-align: center;
}
.div-left {
margin-top: 1.5rem;
margin-left: 1.3rem;
}
.div-right {
margin-top: 1rem;
margin-right: 1.5rem;
}
footer div span a {
color: inherit;
text-decoration: none;
}
.contact-links{
border: 0;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 8px;
padding-right: 8px;
}
.contact-icons{
font-size: 23px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+7
View File
@@ -0,0 +1,7 @@
/*!
* Bootstrap v5.0.2 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},e=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},i=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},n=t=>{const e=i(t);return e&&document.querySelector(e)?e:null},s=t=>{const e=i(t);return e?document.querySelector(e):null},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=e=>r(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?t.findOne(e):null,l=(t,e,i)=>{Object.keys(i).forEach(n=>{const s=i[n],o=e[n],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)})},c=t=>!(!r(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),h=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),d=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?d(t.parentNode):null},u=()=>{},f=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},m=[],g=()=>"rtl"===document.documentElement.dir,_=t=>{var e;e=()=>{const e=p();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",()=>{m.forEach(t=>t())}),m.push(e)):e()},b=t=>{"function"==typeof t&&t()},v=(t,e,i=!0)=>{if(!i)return void b(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const r=({target:i})=>{i===e&&(s=!0,e.removeEventListener("transitionend",r),b(t))};e.addEventListener("transitionend",r),setTimeout(()=>{s||o(e)},n)},y=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},w=/[^.]*(?=\..*)\.|.*/,E=/\..*/,A=/::\d+$/,T={};let O=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},k=/^(mouseenter|mouseleave)/i,L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function D(t){const e=x(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function S(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;s<o;s++){const o=t[n[s]];if(o.originalHandler===e&&o.delegationSelector===i)return o}return null}function I(t,e,i){const n="string"==typeof e,s=n?i:e;let o=M(t);return L.has(o)||(o=t),[n,s,o]}function N(t,e,i,n,s){if("string"!=typeof e||!t)return;if(i||(i=n,n=null),k.test(e)){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=I(e,i,n),l=D(t),c=l[a]||(l[a]={}),h=S(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(w,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parLine truncated
//# sourceMappingURL=bootstrap.bundle.min.js.map
+7
View File
@@ -0,0 +1,7 @@
/*!
* Bootstrap v5.0.2 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(s){if("default"!==s){var i=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,i.get?i:{enumerable:!0,get:function(){return t[s]}})}})),e.default=t,Object.freeze(e)}var s=e(t);const i={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const s=[];let i=t.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(e)&&s.push(i),i=i.parentNode;return s},prev(t,e){let s=t.previousElementSibling;for(;s;){if(s.matches(e))return[s];s=s.previousElementSibling}return[]},next(t,e){let s=t.nextElementSibling;for(;s;){if(s.matches(e))return[s];s=s.nextElementSibling}return[]}},n=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},o=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let s=t.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s="#"+s.split("#")[1]),e=s&&"#"!==s?s.trim():null}return e},r=t=>{const e=o(t);return e&&document.querySelector(e)?e:null},a=t=>{const e=o(t);return e?document.querySelector(e):null},l=t=>{t.dispatchEvent(new Event("transitionend"))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?i.findOne(t):null,d=(t,e,s)=>{Object.keys(s).forEach(i=>{const n=s[i],o=e[i],r=o&&c(o)?"element":null==(a=o)?""+a:{}.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase();var a;if(!new RegExp(n).test(r))throw new TypeError(`${t.toUpperCase()}: Option "${i}" provided type "${r}" but expected type "${n}".`)})},u=t=>!(!c(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),g=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),p=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?p(t.parentNode):null},f=()=>{},m=t=>t.offsetHeight,_=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},b=[],v=()=>"rtl"===document.documentElement.dir,y=t=>{var e;e=()=>{const e=_();if(e){const s=t.NAME,i=e.fn[s];e.fn[s]=t.jQueryInterface,e.fn[s].Constructor=t,e.fn[s].noConflict=()=>(e.fn[s]=i,t.jQueryInterface)}},"loading"===document.readyState?(b.length||document.addEventListener("DOMContentLoaded",()=>{b.forEach(t=>t())}),b.push(e)):e()},w=t=>{"function"==typeof t&&t()},E=(t,e,s=!0)=>{if(!s)return void w(t);const i=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:s}=window.getComputedStyle(t);const i=Number.parseFloat(e),n=Number.parseFloat(s);return i||n?(e=e.split(",")[0],s=s.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(s))):0})(e)+5;let n=!1;const o=({target:s})=>{s===e&&(n=!0,e.removeEventListener("transitionend",o),w(t))};e.addEventListener("transitionend",o),setTimeout(()=>{n||l(e)},i)},A=(t,e,s,i)=>{let n=t.indexOf(e);if(-1===n)return t[!s&&i?t.length-1:0];const o=t.length;return n+=s?1:-1,i&&(n=(n+o)%o),t[Math.max(0,Math.min(n,o-1))]},T=/[^.]*(?=\..*)\.|.*/,C=/\..*/,k=/::\d+$/,L={};let O=1;const D={mouseenter:"mouseover",mouseleave:"mouseout"},I=/^(mouseenter|mouseleave)/i,N=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function S(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function x(t){const e=S(t);return t.uidEvent=e,L[e]=L[e]||{},L[e]}function M(t,e,s=null){const i=Object.keys(t);for(let n=0,o=i.length;n<o;n++){const o=t[i[n]];if(o.originalHandler===e&&o.delegationSelector===s)return o}return null}function P(t,e,s){const i="string"==typeof e,n=i?s:e;let o=R(t);return N.has(o)||(o=t),[i,n,o]}function j(t,e,s,i,n){if("string"!=typeof e||!t)return;if(s||(s=i,i=null),I.test(e)){const t=t=>function(e){if(!e.relatedTarget||e.relatedTaLine truncated
//# sourceMappingURL=bootstrap.min.js.map
+6
View File
@@ -0,0 +1,6 @@
/**
* @popperjs/core v2.9.2 - MIT License
*/
"use strict";!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){function t(e){return{width:(e=e.getBoundingClientRect()).width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function n(e){return null==e?window:"[object Window]"!==e.toString()?(e=e.ownerDocument)&&e.defaultView||window:e}function o(e){return{scrollLeft:(e=n(e)).pageXOffset,scrollTop:e.pageYOffset}}function r(e){return e instanceof n(e).Element||e instanceof Element}function i(e){return e instanceof n(e).HTMLElement||e instanceof HTMLElement}function a(e){return"undefined"!=typeof ShadowRoot&&(e instanceof n(e).ShadowRoot||e instanceof ShadowRoot)}function s(e){return e?(e.nodeName||"").toLowerCase():null}function f(e){return((r(e)?e.ownerDocument:e.document)||window.document).documentElement}function p(e){return t(f(e)).left+o(e).scrollLeft}function c(e){return n(e).getComputedStyle(e)}function l(e){return e=c(e),/auto|scroll|overlay|hidden/.test(e.overflow+e.overflowY+e.overflowX)}function u(e,r,a){void 0===a&&(a=!1);var c=f(r);e=t(e);var u=i(r),d={scrollLeft:0,scrollTop:0},m={x:0,y:0};return(u||!u&&!a)&&(("body"!==s(r)||l(c))&&(d=r!==n(r)&&i(r)?{scrollLeft:r.scrollLeft,scrollTop:r.scrollTop}:o(r)),i(r)?((m=t(r)).x+=r.clientLeft,m.y+=r.clientTop):c&&(m.x=p(c))),{x:e.left+d.scrollLeft-m.x,y:e.top+d.scrollTop-m.y,width:e.width,height:e.height}}function d(e){var n=t(e),o=e.offsetWidth,r=e.offsetHeight;return 1>=Math.abs(n.width-o)&&(o=n.width),1>=Math.abs(n.height-r)&&(r=n.height),{x:e.offsetLeft,y:e.offsetTop,width:o,height:r}}function m(e){return"html"===s(e)?e:e.assignedSlot||e.parentNode||(a(e)?e.host:null)||f(e)}function h(e){return 0<=["html","body","#document"].indexOf(s(e))?e.ownerDocument.body:i(e)&&l(e)?e:h(m(e))}function v(e,t){var o;void 0===t&&(t=[]);var r=h(e);return e=r===(null==(o=e.ownerDocument)?void 0:o.body),o=n(r),r=e?[o].concat(o.visualViewport||[],l(r)?r:[]):r,t=t.concat(r),e?t:t.concat(v(m(r)))}function g(e){return i(e)&&"fixed"!==c(e).position?e.offsetParent:null}function y(e){for(var t=n(e),o=g(e);o&&0<=["table","td","th"].indexOf(s(o))&&"static"===c(o).position;)o=g(o);if(o&&("html"===s(o)||"body"===s(o)&&"static"===c(o).position))return t;if(!o)e:{if(o=-1!==navigator.userAgent.toLowerCase().indexOf("firefox"),-1===navigator.userAgent.indexOf("Trident")||!i(e)||"fixed"!==c(e).position)for(e=m(e);i(e)&&0>["html","body"].indexOf(s(e));){var r=c(e);if("none"!==r.transform||"none"!==r.perspective||"paint"===r.contain||-1!==["transform","perspective"].indexOf(r.willChange)||o&&"filter"===r.willChange||o&&r.filter&&"none"!==r.filter){o=e;break e}e=e.parentNode}o=null}return o||t}function b(e){function t(e){o.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){o.has(e)||(e=n.get(e))&&t(e)})),r.push(e)}var n=new Map,o=new Set,r=[];return e.forEach((function(e){n.set(e.name,e)})),e.forEach((function(e){o.has(e.name)||t(e)})),r}function w(e){var t;return function(){return t||(t=new Promise((function(n){Promise.resolve().then((function(){t=void 0,n(e())}))}))),t}}function x(e){return e.split("-")[0]}function O(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&a(n))do{if(t&&e.isSameNode(t))return!0;t=t.parentNode||t.host}while(t);return!1}function j(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function E(e,r){if("viewport"===r){r=n(e);var a=f(e);r=r.visualViewport;var s=a.clientWidth;a=a.clientHeight;var l=0,u=0;r&&(s=r.width,a=r.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(l=r.offsetLeft,u=r.offsetTop)),e=j(e={width:s,height:a,x:l+p(e),y:u})}else i(r)?((e=t(r)).top+=r.clientTop,e.left+=r.clientLeft,e.bottom=e.top+r.clientHeight,e.right=e.left+r.clientWidth,e.width=r.clientWidth,e.height=r.clientHeight,e.x=e.left,e.y=e.top):(u=f(e),e=f(u),s=o(u),r=null==(a=u.ownerDocument)?void 0:a.body,a=_(e.scrollWidth,e.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),l=_(e.scrollHeight,e.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),u=-s.scrollLeft+p(u),s=-s.scrollTop,"rtl"===c(r||e).direction&&(u+=_(e.clientWidth,r?r.clientWidth:0)-a),e=j({width:a,height:l,x:u,y:s}));return e}function D(e,t,n){return t="clippingParents"===t?function(e){var t=v(m(e)),n=0<=["absolute","fixed"].indexOf(c(e).position)&&i(e)?y(e):e;return r(n)?t.filter((function(e){return r(e)&&O(e,n)&&"body"!==s(e)})):[]}(e):[].concat(t),(n=(n=[].concat(t,[n])).reduce((function(t,n){return n=E(e,n),t.top=_(n.top,t.top),t.right=U(n.right,t.right),t.bottom=U(n.bottom,t.bottom),t.left=_(n.left,t.left),t}),E(e,n[0]))).width=n.right-n.left,n.height=n.bottom-n.top,n.x=n.left,n.y=n.top,n}function L(e){return 0<=["top","bottom"].indexOf(e)?"x":"y"}function P(e){var t=e.reference,n=e.element,o=(e=e.placement)?x(e):null;e=e?e.split("-")[1]:null;var r=t.xLine truncated
//# sourceMappingURL=popper.min.js.map
+30
View File
@@ -0,0 +1,30 @@
{% load static %}
<!-- neye bakıyon lan html var css var neye bakıyon -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<title>{% block title %}{% endblock title %}</title>
</head>
<body>
{% include "components/navbar.html" %}
<div class="container mt-5">
{% block content %}{% endblock content %}
</div>
{% include "components/footer.html" %}
<!-- Bootstrap JS -->
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
{% load static %}
<link rel="stylesheet" href="{% static 'css/footer.css' %}">
<footer>
<div class="div-left" style="float: left;">
<span class="text-muted">
<a href="mailto:ayxan.shahsuvarov1@gmail.com">© 2022 Aykhan Shahsuvarov</a>
</span>
</div>
<div class="div-right" style="float: right;">
<a class="btn btn-outline-light btn-floating m-1 contact-links" target="_blank" href="https://github.com/Ayxan-z" role="button">
<i class="fab fa-github contact-icons"></i>
</a>
<a class="btn btn-outline-light btn-floating m-1 contact-links" target="_blank" href="https://www.linkedin.com/in/ayxan-shahsuvarov-59a314187" role="button">
<i class="fab fa-linkedin-in contact-icons"></i>
</a>
<a class="btn btn-outline-light btn-floating m-1 contact-links" target="_blank" href="mailto:ayxan.shahsuvarov1@gmail.com" role="button">
<i class="fa fa-envelope contact-icons"></i>
</a>
</div>
</footer>
+9
View File
@@ -0,0 +1,9 @@
{% if messages %}
{% for message in messages %}
{% if message.tags == "info" %}
<div class="alert alert-danger">{{ message }}</div>
{% else %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endif %}
{% endfor %}
{% endif %}
+30
View File
@@ -0,0 +1,30 @@
{% load static %}
<nav class="navbar navbar-expand-lg bg-dark navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/" style="padding-top: 2px; padding-bottom: 2px;">
<img src="{% static 'icons/retro-tv.png' %}" width="55px" height="55px">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="me-auto"></ul>
{% if request.user.is_authenticated == True %}
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{% url 'profile' %}" style="font-size: 18px;">
Profile
</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{% url 'logout' %}" style="font-size: 18px;">
Logout
</a>
</li>
</ul>
{% endif %}
</div>
</div>
</nav>