Add docstrings
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Silver Ghost 2023-02-10 12:54:15 +03:00
parent 520acb9477
commit 8e182fd08e
No known key found for this signature in database
42 changed files with 163 additions and 33 deletions

View File

@ -25,3 +25,8 @@ repos:
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/econchick/interrogate
rev: 1.5.0
hooks:
- id: interrogate
args: [--quiet, --fail-under=95]

View File

@ -1,3 +1,4 @@
"""Later42 is a Django app that allows you to schedule tasks for later execution."""
from .celery import app as celery_app
__all__ = ("celery_app",)

View File

@ -1,3 +1,4 @@
"""Admin configuration for later42 app."""
from django.contrib import admin
from .models.urls import URL

View File

@ -1,3 +1,4 @@
"""Celery configuration module."""
import os
from celery import Celery
@ -15,8 +16,3 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f"Request: {self.request!r}")

View File

@ -1,11 +1,13 @@
from django.contrib.auth.forms import AuthenticationForm
"""Forms for later42 app."""
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class SignUpForm(UserCreationForm):
"""Sign up form."""
def __init__(self, *args, **kwargs):
"""Init method."""
super().__init__(*args, **kwargs)
self.fields["username"].widget.attrs.update({"class": "form-control"})
self.fields["password1"].widget.attrs.update({"class": "form-control"})
@ -13,6 +15,8 @@ class SignUpForm(UserCreationForm):
self.fields["username"].label = "Логин"
class Meta:
"""Meta."""
model = User
fields = (
"username",
@ -23,7 +27,10 @@ class SignUpForm(UserCreationForm):
class CustomLoginForm(AuthenticationForm):
"""Custom login form."""
def __init__(self, *args, **kwargs):
"""Init method."""
super().__init__(*args, **kwargs)
self.fields["username"].widget.attrs.update({"class": "form-control"})
self.fields["password"].widget.attrs.update({"class": "form-control"})

View File

@ -0,0 +1 @@
"""Libs for later42"""

View File

@ -1,8 +1,10 @@
"""Content module."""
from bs4 import BeautifulSoup
from newspaper import Article, Config
def sanitize_img_size(html: str):
"""Sanitize image size."""
soup = BeautifulSoup(html, "html.parser")
for img in soup.find_all("img"):
img["width"] = "100%"
@ -11,6 +13,7 @@ def sanitize_img_size(html: str):
def get_content(url: str):
"""Get content."""
config = Config()
config.keep_article_html = True
article = Article(url, config=config)

View File

@ -0,0 +1 @@
"""Loging module"""

View File

@ -1,10 +1,18 @@
"""Logging middleware for debugging purposes."""
class ExceptionLoggingMiddleware(object):
"""Middleware for logging exceptions."""
def __init__(self, get_response):
"""Init method."""
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
"""
Code to be executed for each request before
the view (and later middleware) are called.
"""
print(request.body)
print(request.scheme)
print(request.method)

View File

@ -1,11 +1,13 @@
# Generated by Django 4.1.2 on 2022-10-12 10:05
"""Generated by Django 4.1.2 on 2022-10-12 10:05"""
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
"""Migration"""
initial = True
dependencies = [

View File

@ -1,9 +1,11 @@
# Generated by Django 4.1.2 on 2022-10-24 13:04
"""Generated by Django 4.1.2 on 2022-10-24 13:04"""
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0001_initial"),
]

View File

@ -1,9 +1,11 @@
# Generated by Django 4.1.2 on 2022-10-25 08:05
"""Generated by Django 4.1.2 on 2022-10-25 08:05"""
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0002_url_archived"),
]

View File

@ -1,10 +1,12 @@
# Generated by Django 4.1.2 on 2022-11-05 17:10
"""Generated by Django 4.1.2 on 2022-11-05 17:10"""
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0003_url_content"),
]

View File

@ -1,9 +1,11 @@
# Generated by Django 4.1.2 on 2022-11-05 17:11
"""Generated by Django 4.1.2 on 2022-11-05 17:11"""
from django.db import migrations
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0004_articles"),
]

View File

@ -1,9 +1,11 @@
# Generated by Django 4.1.2 on 2022-11-14 12:28
"""Generated by Django 4.1.2 on 2022-11-14 12:28"""
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0005_rename_articles_article"),
]

View File

@ -1,9 +1,11 @@
# Generated by Django 4.1.3 on 2022-11-30 06:10
"""Generated by Django 4.1.3 on 2022-11-30 06:10"""
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration"""
dependencies = [
("later42", "0006_remove_url_content_remove_url_title_article_short_and_more"),
]

View File

@ -0,0 +1 @@
"""Migrations for later42."""

View File

@ -0,0 +1 @@
"""Models for later42."""

View File

@ -1,8 +1,11 @@
"""Article model.""" ""
from django.db import models
from later42.models.urls import URL
class Article(models.Model):
"""Article model."""
id = models.AutoField(auto_created=True, primary_key=True)
url = models.ForeignKey(URL, on_delete=models.CASCADE)
content = models.TextField(blank=True, null=True)

View File

@ -1,3 +1,4 @@
"""URL model."""
from django.contrib.auth.models import User
from django.db import models
@ -5,6 +6,8 @@ User._meta.get_field("email")._unique = True
class URL(models.Model):
"""URL model."""
id = models.AutoField(auto_created=True, primary_key=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
url = models.CharField(max_length=2000)

View File

@ -1,14 +1,24 @@
from django.contrib.auth.models import User, Group
"""Serializers for later42 app."""
from django.contrib.auth.models import Group, User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
"""User serializer"""
class Meta:
"""Meta"""
model = User
fields = ["url", "username", "email", "groups"]
class GroupSerializer(serializers.HyperlinkedModelSerializer):
"""Group serializer"""
class Meta:
"""Meta"""
model = Group
fields = ["url", "name"]

View File

@ -1,3 +1,4 @@
"""Celery tasks"""
import os
import pybrake
@ -23,6 +24,7 @@ if AIRBRAKE_PROJECT_ID is not None and AIRBRAKE_PROJECT_KEY is not None:
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def get_url_content_task(self, url, user_id):
"""Get content from url and save it to database"""
data = get_content(url)
user = User.objects.get(pk=int(user_id))

View File

@ -0,0 +1 @@
"""Tests"""

View File

@ -0,0 +1 @@
"""Tests for later42."""

View File

@ -1,3 +1,4 @@
"""Test API views."""
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
@ -6,7 +7,10 @@ from rest_framework.test import APIClient
class ApiTests(TestCase):
"""Test API views."""
def setUp(self) -> None:
"""Set up test data."""
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
@ -22,6 +26,7 @@ class ApiTests(TestCase):
self.token = Token.objects.create(user=self.user)
def test_url_create(self):
"""Test the url creation."""
token = Token.objects.get(user=self.user)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)

View File

@ -1,11 +1,16 @@
"""Test the api_token view."""
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.test import Client, TestCase
from django.urls import reverse
from rest_framework.authtoken.models import Token
class ApiKeyTests(TestCase):
"""Test the api_token view."""
def setUp(self) -> None:
"""Set up the test environment."""
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
@ -19,14 +24,17 @@ class ApiKeyTests(TestCase):
self.response = self.c.get(reverse("api_token"))
def test_api_key_creation(self):
"""Test the api_token view."""
assert self.response.status_code == 302
assert self.response.url == "/profile/"
def test_api_key_created(self):
"""Test the api_token creation."""
token = Token.objects.get(user=self.user)
assert token is not None
def test_api_key_reset(self):
"""Test the api_token reset."""
token_old = Token.objects.get(user=self.user)
self.response = self.c.get(reverse("api_token"))
token_new = Token.objects.get(user=self.user)

View File

@ -1,3 +1,4 @@
"""Test index view."""
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
@ -6,7 +7,10 @@ from later42.models.urls import URL
class IndexTests(TestCase):
"""Test index view."""
def setUp(self) -> None:
"""Set up test data."""
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
@ -19,6 +23,7 @@ class IndexTests(TestCase):
self.assertTrue(self.user.is_authenticated)
def test_url_delete(self):
"""Test the url deletion."""
url = URL.objects.create(url="https://www.google.com/", user=self.user)
url.save()
self.c.delete(reverse("delete", kwargs={"url_id": url.id}))

View File

@ -1,10 +1,14 @@
"""Test login page and profile page"""
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.test import Client, TestCase
from django.urls import reverse
class LoginPageTests(TestCase):
"""Test login page and profile page"""
def setUp(self) -> None:
"""Set up test data"""
self.username = "testuser1"
self.email = "testuser1@email.com"
self.password = "password1234567QWERTY"
@ -17,10 +21,12 @@ class LoginPageTests(TestCase):
self.assertTrue(self.user.is_authenticated)
def test_login_page(self):
"""Test login page"""
response = self.client.get(reverse("login"))
assert response.status_code == 200
def test_index_page_after_login(self):
"""Test index page after login"""
response = self.c.get(reverse("index"))
assert response.status_code == 200
assert response.context["user"].is_authenticated
@ -28,11 +34,13 @@ class LoginPageTests(TestCase):
self.assertContains(response, "/accounts/logout/")
def test_profile_page_template(self):
"""Test profile page template"""
response = self.c.get(reverse("profile"))
assert response.status_code == 200
self.assertTemplateUsed(response, "profile.html")
def test_api_key_creation_button(self):
"""Test api key creation button"""
response = self.c.get(reverse("profile"))
assert response.status_code == 200
self.assertContains(response, 'id="createbutton"')

View File

@ -1,19 +1,24 @@
"""Test pages views."""
from django.test import TestCase
from django.urls import reverse
class PageTests(TestCase):
"""Test pages views."""
def test_index_page_url(self):
"""Test index page url"""
response = self.client.get(reverse("index"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name="index.html")
def test_about_page_url(self):
"""Test about page url"""
response = self.client.get(reverse("about"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name="about.html")
def test_profile_page_url_redirect(self):
"""Test profile page url redirect"""
response = self.client.get(reverse("profile"))
self.assertEqual(response.status_code, 302)

View File

@ -1,26 +1,32 @@
from django.test import TestCase
"""Test signup view."""
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
class SignUpPageTests(TestCase):
"""Test signup view."""
def setUp(self) -> None:
"""Set up test data."""
self.username = "testuser"
self.email = "testuser@email.com"
self.password = "password1234567QWERTY"
def test_signup_page_url(self):
"""Test the signup page url."""
response = self.client.get(reverse("signup"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name="registration/signup.html")
def test_signup_page_view_name(self):
"""Test the signup page view name."""
response = self.client.get(reverse("signup"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name="registration/signup.html")
def test_signup_form(self):
"""Test the signup form."""
response = self.client.post(
reverse("signup"),
data={

View File

@ -1,9 +1,13 @@
"""Token generator for user activation.""" ""
from django.contrib.auth.tokens import PasswordResetTokenGenerator
import six
class TokenGenerator(PasswordResetTokenGenerator):
"""Token generator for user activation."""
def _make_hash_value(self, user, timestamp):
"""Make hash value for user activation."""
return (
six.text_type(user.pk)
+ six.text_type(timestamp)

View File

@ -14,35 +14,39 @@ Including another URLconf
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 import views
from django.urls import include, path
from rest_framework import routers, serializers, viewsets
# from later42.forms import CustomLoginForm
from later42.views import (
about,
account_activation,
index,
profile,
api,
api_token,
index,
profile,
reader,
search,
signup,
about,
)
# from django.contrib.auth import views
class UserSerializer(serializers.HyperlinkedModelSerializer):
"""User serializer"""
class Meta:
"""Meta"""
model = User
fields = ["url", "username", "email", "is_staff"]
class UserViewSet(viewsets.ModelViewSet):
"""User viewset"""
queryset = User.objects.all()
serializer_class = UserSerializer

View File

@ -0,0 +1 @@
"""Views for the later42 app."""

View File

@ -1,5 +1,7 @@
"""About page view."""
from django.shortcuts import render
def get(request):
"""About page view."""
return render(request, "about.html")

View File

@ -1,3 +1,4 @@
"""Account activation view."""
import django
from django.contrib.auth import login
from django.contrib.auth.models import User
@ -12,6 +13,7 @@ django.utils.encoding.force_text = force_str
def activate(request, uidb64, token):
"""Activate user account."""
try:
uid = django.utils.encoding.force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)

View File

@ -1,3 +1,4 @@
"""API views for later42."""
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from rest_framework.response import Response
@ -8,6 +9,7 @@ from later42.tasks import get_url_content_task
def validate_url(to_validate: str) -> bool:
"""Validate url."""
validator = URLValidator()
try:
validator(to_validate)
@ -17,7 +19,10 @@ def validate_url(to_validate: str) -> bool:
class URL(APIView):
"""URL API view."""
def post(self, request, format=None):
"""Post url to be processed."""
if validate_url(request.GET.get("url")):
get_url_content_task.delay(request.GET.get("url"), request.user.id)
return Response({"status": "success"})
@ -25,6 +30,7 @@ class URL(APIView):
return Response({"status": "error"})
def delete(self, request, format=None):
"""Delete url from database."""
id = request.GET.get("id")
if id:
url = URLModel.objects.filter(id=id).first()

View File

@ -1,3 +1,4 @@
"""API Token Views."""
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from rest_framework.authtoken.models import Token
@ -5,6 +6,7 @@ from rest_framework.authtoken.models import Token
@login_required
def create(request):
"""Create API Token for user."""
token = Token.objects.filter(user=request.user)
if len(token) > 0:
token.delete()

View File

@ -1,3 +1,4 @@
"""Index view."""
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
@ -8,6 +9,7 @@ from later42.models.urls import URL
def get(request):
"""Index view."""
data = {}
try:
urls = URL.objects.filter(user=request.user, archived=False).order_by("-id")
@ -22,6 +24,7 @@ def get(request):
@login_required
def archive(request, url_id=None):
"""Archive view."""
if url_id:
URL.objects.filter(id=url_id, user=request.user).update(archived=True)
return redirect("index")
@ -41,5 +44,6 @@ def archive(request, url_id=None):
@login_required
def delete(request, url_id):
"""Delete url from database."""
URL.objects.filter(id=url_id, user=request.user).delete()
return redirect("archive")

View File

@ -1,3 +1,4 @@
"""Profile view."""
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.shortcuts import render
@ -6,6 +7,7 @@ from rest_framework.authtoken.models import Token
@login_required
def get(request):
"""Profile view."""
token = Token.objects.filter(user=request.user)
user = User.objects.get(username=request.user)

View File

@ -1,5 +1,7 @@
"""Reader view."""
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from later42.libs.content import get_content, sanitize_img_size
from later42.models.article import Article
from later42.models.urls import URL
@ -7,6 +9,7 @@ from later42.models.urls import URL
@login_required
def get(request, url_id=None):
"""Reader view."""
url = URL.objects.get(user=request.user, id=url_id)
content = {}
try:

View File

@ -1,3 +1,4 @@
"""Search view."""
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
@ -9,6 +10,7 @@ from later42.models.article import Article
@login_required
def search(request):
"""Search view."""
pattern = request.POST.get("search")
context = {}
if request.method == "GET":

View File

@ -1,3 +1,4 @@
"""Signup view."""
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
@ -11,6 +12,7 @@ from later42.tokens import account_activation_token
def register(request):
"""Register user."""
if request.method == "POST":
form = SignUpForm(request.POST)
if form.is_valid():