feat: Initial commit

This commit is contained in:
Silver Ghost 2022-10-12 22:32:57 +03:00
commit edec9a627a
No known key found for this signature in database
39 changed files with 12115 additions and 0 deletions

141
.gitignore vendored Normal file
View 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
View 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
db/.keep Normal file
View File

0
later42/__init__.py Normal file
View File

5
later42/admin.py Normal file
View 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
View 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
View 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 = 'Логин'

View File

18
later42/logging/debug.py Normal file
View 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

View 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)),
],
),
]

View File

View File

9
later42/models/urls.py Normal file
View 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
View 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
View 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',
),
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

11248
later42/static/css/styles.css Normal file

File diff suppressed because it is too large Load Diff

View 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;
});
})

View 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">&copy; 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>

View File

@ -0,0 +1,2 @@
Test content block
<p></p>

View 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 %}

View 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 %}

View 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 %}

View 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
View 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'),
]

View File

37
later42/views/api.py Normal file
View 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'})

View 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
View 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
View 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
View 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
View 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
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', '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
View 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