feat: Initial commit
This commit is contained in:
commit
edec9a627a
141
.gitignore
vendored
Normal file
141
.gitignore
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
db/*.sqlite3
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 DNTSK
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
0
later42/__init__.py
Normal file
0
later42/__init__.py
Normal file
5
later42/admin.py
Normal file
5
later42/admin.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models.urls import URL
|
||||||
|
|
||||||
|
admin.site.register(URL)
|
16
later42/asgi.py
Normal file
16
later42/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for bookmarks 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', 'later42.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
35
later42/forms.py
Normal file
35
later42/forms.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
|
|
||||||
|
|
||||||
|
class SignUpForm(UserCreationForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['username'].widget.attrs.update(
|
||||||
|
{'class': 'form-control'}
|
||||||
|
)
|
||||||
|
self.fields['password1'].widget.attrs.update(
|
||||||
|
{'class': 'form-control'}
|
||||||
|
)
|
||||||
|
self.fields['password2'].widget.attrs.update(
|
||||||
|
{'class': 'form-control'}
|
||||||
|
)
|
||||||
|
self.fields['username'].label = 'Логин'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'email', 'password1', 'password2',)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomLoginForm(AuthenticationForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['username'].widget.attrs.update(
|
||||||
|
{'class': 'form-control'}
|
||||||
|
)
|
||||||
|
self.fields['password'].widget.attrs.update(
|
||||||
|
{'class': 'form-control'}
|
||||||
|
)
|
||||||
|
self.fields['username'].label = 'Логин'
|
0
later42/logging/__init__.py
Normal file
0
later42/logging/__init__.py
Normal file
18
later42/logging/debug.py
Normal file
18
later42/logging/debug.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class ExceptionLoggingMiddleware(object):
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
|
||||||
|
# Code to be executed for each request before
|
||||||
|
# the view (and later middleware) are called.
|
||||||
|
print(request.body)
|
||||||
|
print(request.scheme)
|
||||||
|
print(request.method)
|
||||||
|
print(request.META)
|
||||||
|
print(request.headers)
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
return response
|
26
later42/migrations/0001_initial.py
Normal file
26
later42/migrations/0001_initial.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 4.1.2 on 2022-10-12 10:05
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='URL',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('url', models.CharField(max_length=2000)),
|
||||||
|
('title', models.CharField(max_length=2000)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
later42/migrations/__init__.py
Normal file
0
later42/migrations/__init__.py
Normal file
0
later42/models/__init__.py
Normal file
0
later42/models/__init__.py
Normal file
9
later42/models/urls.py
Normal file
9
later42/models/urls.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class URL(models.Model):
|
||||||
|
id = models.AutoField(auto_created=True, primary_key=True)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
url = models.CharField(max_length=2000)
|
||||||
|
title = models.CharField(max_length=2000)
|
14
later42/serializers.py
Normal file
14
later42/serializers.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['url', 'username', 'email', 'groups']
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
fields = ['url', 'name']
|
142
later42/settings.py
Normal file
142
later42/settings.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
"""
|
||||||
|
Django settings for bookmarks project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.1.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.1/ref/settings/
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = os.getenv('SECRET', 'django-insecure-c%g@wujt4dco#e%k-!25o3)0%t+wm5=k%l(m!kk^p_g%kknod!')
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'later42',
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
# 'later42.logging.debug.ExceptionLoggingMiddleware',
|
||||||
|
'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 = 'later42.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'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 = 'later42.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db/db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'ru-ru'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "later42/static",
|
||||||
|
]
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
LOGIN_REDIRECT_URL = "index"
|
||||||
|
LOGOUT_REDIRECT_URL = "index"
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
|
'PAGE_SIZE': 10,
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
),
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
),
|
||||||
|
}
|
BIN
later42/static/assets/favicon.ico
Normal file
BIN
later42/static/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
later42/static/assets/img/about-bg.jpg
Normal file
BIN
later42/static/assets/img/about-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 MiB |
BIN
later42/static/assets/img/contact-bg.jpg
Normal file
BIN
later42/static/assets/img/contact-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 489 KiB |
BIN
later42/static/assets/img/home-bg.jpg
Normal file
BIN
later42/static/assets/img/home-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 984 KiB |
BIN
later42/static/assets/img/post-bg.jpg
Normal file
BIN
later42/static/assets/img/post-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 MiB |
BIN
later42/static/assets/img/post-sample-image.jpg
Normal file
BIN
later42/static/assets/img/post-sample-image.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
11248
later42/static/css/styles.css
Normal file
11248
later42/static/css/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
29
later42/static/js/scripts.js
Normal file
29
later42/static/js/scripts.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*!
|
||||||
|
* Start Bootstrap - Clean Blog v6.0.8 (https://startbootstrap.com/theme/clean-blog)
|
||||||
|
* Copyright 2013-2022 Start Bootstrap
|
||||||
|
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let scrollPos = 0;
|
||||||
|
const mainNav = document.getElementById('mainNav');
|
||||||
|
const headerHeight = mainNav.clientHeight;
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
const currentTop = document.body.getBoundingClientRect().top * -1;
|
||||||
|
if ( currentTop < scrollPos) {
|
||||||
|
// Scrolling Up
|
||||||
|
if (currentTop > 0 && mainNav.classList.contains('is-fixed')) {
|
||||||
|
mainNav.classList.add('is-visible');
|
||||||
|
} else {
|
||||||
|
console.log(123);
|
||||||
|
mainNav.classList.remove('is-visible', 'is-fixed');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Scrolling Down
|
||||||
|
mainNav.classList.remove(['is-visible']);
|
||||||
|
if (currentTop > headerHeight && !mainNav.classList.contains('is-fixed')) {
|
||||||
|
mainNav.classList.add('is-fixed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scrollPos = currentTop;
|
||||||
|
});
|
||||||
|
})
|
98
later42/templates/base.html
Normal file
98
later42/templates/base.html
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="Ссылки для отложенного чтения. Сохрани ссылки для отложенного чтения, если нет времени прочитать сейчас." />
|
||||||
|
<meta name="author" content="Silver Ghost" />
|
||||||
|
<title>Later 42: {% block title %}Сохрани ссылки, чтобы прочитать позже{% endblock %}</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
||||||
|
<!-- Font Awesome icons (free version)-->
|
||||||
|
<script src="https://use.fontawesome.com/releases/v6.1.0/js/all.js" crossorigin="anonymous"></script>
|
||||||
|
<!-- Google fonts-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Core theme CSS (includes Bootstrap)-->
|
||||||
|
<link href="{% static 'css/styles.css' %}" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation-->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<a class="navbar-brand" href="{% url 'index' %}">Later 42</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
Menu
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
|
<ul class="navbar-nav ms-auto py-4 py-lg-0">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'profile' %}">Профиль</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'logout' %}">Выйти</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'signup' %}">Регистрация</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'login' %}">Войти</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- Page Header-->
|
||||||
|
<header class="masthead" style="background-image: url('{% static 'assets/img/home-bg.jpg' %}')">
|
||||||
|
<div class="container position-relative px-4 px-lg-5">
|
||||||
|
<div class="row gx-4 gx-lg-5 justify-content-center">
|
||||||
|
<div class="col-md-10 col-lg-8 col-xl-7">
|
||||||
|
<div class="site-heading">
|
||||||
|
<h1>Later 42</h1>
|
||||||
|
<span class="subheading">Сохрани ссылки, чтобы прочитать позже</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Main Content-->
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<div class="row gx-4 gx-lg-5 justify-content-center">
|
||||||
|
<div class="col-md-10 col-lg-8 col-xl-7">
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer-->
|
||||||
|
<footer class="border-top">
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<div class="row gx-4 gx-lg-5 justify-content-center">
|
||||||
|
<div class="col-md-10 col-lg-8 col-xl-7">
|
||||||
|
<ul class="list-inline text-center">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="https://dntsk.dev">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fa-solid fa-location-dot fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<a href="#!">
|
||||||
|
<span class="fa-stack fa-lg">
|
||||||
|
<i class="fas fa-circle fa-stack-2x"></i>
|
||||||
|
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="small text-center text-muted fst-italic">© dntsk.dev, 2022</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- Bootstrap core JS-->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- Core theme JS-->
|
||||||
|
<script src="{% static 'js/scripts.js' %}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
later42/templates/docs.html
Normal file
2
later42/templates/docs.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Test content block
|
||||||
|
<p></p>
|
20
later42/templates/index.html
Normal file
20
later42/templates/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if not user.is_authenticated %}
|
||||||
|
{% include 'docs.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% for url in urls %}
|
||||||
|
<!-- Post preview-->
|
||||||
|
<div class="post-preview">
|
||||||
|
<a href="{{ url.url }}">
|
||||||
|
<h2 class="post-title">{{ url.title }}</h2>
|
||||||
|
</a>
|
||||||
|
<p class="post-meta">
|
||||||
|
{{ url.url }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Divider-->
|
||||||
|
<hr class="my-4" />
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
51
later42/templates/profile.html
Normal file
51
later42/templates/profile.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Настройки{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2 class="center-block">Профиль</h2>
|
||||||
|
|
||||||
|
<p> </p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-start">
|
||||||
|
<div class="col">
|
||||||
|
Логин:
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ user.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row align-items-start">
|
||||||
|
<div class="col">
|
||||||
|
Email:
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ user.email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p> </p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-start">
|
||||||
|
<div class="col">
|
||||||
|
API ключ:
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% if token is not None %}
|
||||||
|
{{ token }}
|
||||||
|
<form action="{% url 'api_token' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Сбросить на новый">
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form action="{% url 'api_token' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Создать">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p></p>
|
||||||
|
{% endblock %}
|
12
later42/templates/registration/login.html
Normal file
12
later42/templates/registration/login.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Вход</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button class="btn btn-primary text-uppercase" type="submit">Войти</button>
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
{% endblock %}
|
||||||
|
|
24
later42/templates/signup.html
Normal file
24
later42/templates/signup.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>Регистрация</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<p>
|
||||||
|
{{ field.label_tag }}<br>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<small class="text-muted">{{ field.help_text }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p style="color: red">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn btn-primary text-uppercase" type="submit">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
{% endblock %}
|
53
later42/urls.py
Normal file
53
later42/urls.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""later42 URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth.views import LoginView
|
||||||
|
|
||||||
|
from rest_framework import routers, serializers, viewsets
|
||||||
|
|
||||||
|
from later42.forms import CustomLoginForm
|
||||||
|
from later42.views import index, profile, api, api_token, signup
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['url', 'username', 'email', 'is_staff']
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = User.objects.all()
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'users', UserViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('signup/', signup.register, name='signup'),
|
||||||
|
path("accounts/login/", LoginView.as_view(authentication_form=CustomLoginForm), name="login"),
|
||||||
|
path('accounts/', include('django_registration.backends.activation.urls')),
|
||||||
|
path("accounts/", include("django.contrib.auth.urls")),
|
||||||
|
path('api/url/', api.URL.as_view(), name='urls'),
|
||||||
|
path('', index.get, name='index'),
|
||||||
|
path('delete/<int:url_id>', index.delete, name='delete'),
|
||||||
|
path('profile/', profile.get, name='profile'),
|
||||||
|
path('api_token/', api_token.create, name='api_token'),
|
||||||
|
]
|
0
later42/views/__init__.py
Normal file
0
later42/views/__init__.py
Normal file
37
later42/views/api.py
Normal file
37
later42/views/api.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from later42.models.urls import URL as URLModel
|
||||||
|
|
||||||
|
|
||||||
|
class URL(APIView):
|
||||||
|
def get_title(self, url):
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
title = soup.find('title').text
|
||||||
|
return title
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
url = request.GET.get('url')
|
||||||
|
if url:
|
||||||
|
title = self.get_title(url)
|
||||||
|
if title is None:
|
||||||
|
title = url
|
||||||
|
url = URLModel(url=url, title=title, user=request.user)
|
||||||
|
url.save()
|
||||||
|
return Response({'status': 'success'})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'error'})
|
||||||
|
|
||||||
|
def delete(self, request, format=None):
|
||||||
|
id = request.GET.get('id')
|
||||||
|
if id:
|
||||||
|
URLModel.objects.filter(id=id).delete()
|
||||||
|
return Response({'status': 'success'})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'error'})
|
13
later42/views/api_token.py
Normal file
13
later42/views/api_token.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.shortcuts import redirect
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
def create(request):
|
||||||
|
token = Token.objects.filter(user=request.user)
|
||||||
|
try:
|
||||||
|
token.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
token = Token.objects.create(user=request.user)
|
||||||
|
token.save()
|
||||||
|
return redirect('profile')
|
18
later42/views/index.py
Normal file
18
later42/views/index.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from later42.models.urls import URL
|
||||||
|
|
||||||
|
|
||||||
|
def get(request):
|
||||||
|
try:
|
||||||
|
urls = URL.objects.filter(user=request.user).order_by('-id')
|
||||||
|
context = {'urls': urls}
|
||||||
|
except:
|
||||||
|
context = {'urls': []}
|
||||||
|
return render(request, 'index.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def delete(request, url_id):
|
||||||
|
URL.objects.filter(id=url_id, user=request.user).delete()
|
||||||
|
return redirect('index')
|
18
later42/views/profile.py
Normal file
18
later42/views/profile.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.shortcuts import render
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def get(request):
|
||||||
|
token = Token.objects.filter(user=request.user)
|
||||||
|
user = User.objects.get(username=request.user)
|
||||||
|
|
||||||
|
if token:
|
||||||
|
context = {'token': token[0], 'user': user}
|
||||||
|
else:
|
||||||
|
context = {'token': None, 'user': user}
|
||||||
|
|
||||||
|
return render(request, 'profile.html', context)
|
||||||
|
|
21
later42/views/signup.py
Normal file
21
later42/views/signup.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from later42.forms import SignUpForm
|
||||||
|
|
||||||
|
|
||||||
|
def register(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = SignUpForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
user = form.save()
|
||||||
|
user.refresh_from_db()
|
||||||
|
user.save()
|
||||||
|
raw_password = form.cleaned_data.get('password1')
|
||||||
|
|
||||||
|
user = authenticate(username=user.username, password=raw_password)
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
|
return redirect('index')
|
||||||
|
else:
|
||||||
|
form = SignUpForm()
|
||||||
|
return render(request, 'signup.html', {'form': form})
|
16
later42/wsgi.py
Normal file
16
later42/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for bookmarks 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', 'later42.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
manage.py
Executable file
22
manage.py
Executable 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', 'later42.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()
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Django==4.1.2
|
||||||
|
gunicorn==20.1.0
|
||||||
|
loguru==0.6.0
|
||||||
|
bs4==0.0.1
|
||||||
|
requests==2.28.1
|
||||||
|
djangorestframework==3.14.0
|
Loading…
Reference in New Issue
Block a user