added base signal and model for slug generation, updated about app

This commit is contained in:
Alexander Karpov 2023-03-24 12:20:58 +03:00
parent 502703117b
commit 359e2929fc
28 changed files with 813 additions and 60 deletions

View File

5
akarpov/about/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from akarpov.about.models import Project
admin.site.register(Project)

7
akarpov/about/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class AboutConfig(AppConfig):
name = "akarpov.about"
verbose_name = _("About me app")

View File

@ -0,0 +1,49 @@
# Generated by Django 4.1.7 on 2023-03-24 07:12
from django.db import migrations, models
import django_extensions.db.fields
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Project",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
),
),
("name", models.CharField(max_length=30)),
("description", models.TextField(blank=True)),
("md", models.TextField(blank=True)),
("image", models.ImageField(upload_to="uploads/images/")),
("source_link", models.URLField(blank=True)),
("view_link", models.URLField(blank=True)),
],
options={
"ordering": ("-modified",),
},
),
]

View File

21
akarpov/about/models.py Normal file
View File

@ -0,0 +1,21 @@
from django.db import models
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel
class Project(TimeStampedModel):
name = models.CharField(max_length=30)
description = models.TextField(blank=True)
md = models.TextField(blank=True)
image = models.ImageField(upload_to="uploads/images/", blank=False)
source_link = models.URLField(blank=True)
view_link = models.URLField(blank=True)
def get_absolute_url(self):
return reverse("about:project", kwargs={"pk": self.pk})
def __str__(self):
return self.name
class Meta:
ordering = ("-modified",)

10
akarpov/about/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = "about"
urlpatterns = [
path("", views.about_view, name="about"),
path("<int:pk>", views.project_view, name="project"),
]

26
akarpov/about/views.py Normal file
View File

@ -0,0 +1,26 @@
from django.views.generic import DetailView, TemplateView
from akarpov.about.models import Project
class AboutView(TemplateView):
template_name = "about/about.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["projects"] = Project.objects.all()
return context
about_view = AboutView.as_view()
class ProjectView(DetailView):
model = Project
slug_field = "pk"
slug_url_kwarg = "pk"
template_name = "about/project.html"
project_view = ProjectView.as_view()

View File

@ -2,9 +2,43 @@
from akarpov.blog.models import Post, PostRating, Tag from akarpov.blog.models import Post, PostRating, Tag
# Register your models here.
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "post_views")
list_filter = ("creator",)
readonly_fields = ("created", "edited")
fieldsets = (
(None, {"fields": ("title", "body", "creator", "tags")}),
(
"Ratings",
{
"classes": ("collapse",),
"fields": (
"post_views",
"rating",
"rating_up",
"rating_down",
"comment_count",
),
},
),
(
"Images",
{
"classes": ("collapse",),
"fields": ("image", "image_cropped"),
},
),
(
"Urls",
{
"classes": ("collapse",),
"fields": ("slug", "short_link"),
},
),
)
admin.site.register(Post) admin.site.register(Post, PostAdmin)
admin.site.register(Tag) admin.site.register(Tag)
admin.site.register(PostRating) admin.site.register(PostRating)

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-03-24 08:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("blog", "0004_alter_post_short_link"),
]
operations = [
migrations.AlterField(
model_name="post",
name="slug",
field=models.SlugField(blank=True, max_length=20, unique=True),
),
]

View File

@ -1,7 +1,7 @@
from ckeditor_uploader.fields import RichTextUploadingField from ckeditor_uploader.fields import RichTextUploadingField
from colorfield.fields import ColorField from colorfield.fields import ColorField
from django.db import models from django.db import models
from django.db.models import Count, SlugField from django.db.models import Count
from django.urls import reverse from django.urls import reverse
from akarpov.common.models import BaseImageModel from akarpov.common.models import BaseImageModel
@ -15,7 +15,6 @@ class Post(BaseImageModel, ShortLink):
body = RichTextUploadingField(blank=False) body = RichTextUploadingField(blank=False)
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts") creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
slug = SlugField(max_length=20, blank=True)
post_views = models.IntegerField(default=0) post_views = models.IntegerField(default=0)
rating = models.IntegerField(default=0) rating = models.IntegerField(default=0)
@ -66,6 +65,9 @@ def get_absolute_url(self):
class Meta: class Meta:
ordering = ["-created"] ordering = ["-created"]
class SlugMeta:
slug_length = 3
class Tag(models.Model): class Tag(models.Model):
name = models.CharField(max_length=20, unique=True) name = models.CharField(max_length=20, unique=True)

View File

@ -3,17 +3,12 @@
from akarpov.blog.models import Post, PostRating, Tag from akarpov.blog.models import Post, PostRating, Tag
from akarpov.common.tasks import crop_model_image from akarpov.common.tasks import crop_model_image
from akarpov.utils.generators import generate_charset, generate_hex_color from akarpov.utils.generators import generate_hex_color
@receiver(pre_save, sender=Post) @receiver(pre_save, sender=Post)
def post_on_save(sender, instance: Post, **kwargs): def post_on_save(sender, instance: Post, **kwargs):
if instance.id is None: if instance.id:
slug = generate_charset(3)
while Post.objects.filter(slug=slug).exists():
slug = generate_charset(3)
instance.slug = slug
else:
previous = Post.objects.get(id=instance.id) previous = Post.objects.get(id=instance.id)
if ( if (
previous.image != instance.image previous.image != instance.image

View File

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from akarpov.utils.files import user_file_upload_mixin from akarpov.utils.files import user_file_upload_mixin
from akarpov.utils.generators import generate_charset
class BaseImageModel(models.Model): class BaseImageModel(models.Model):
@ -9,3 +10,54 @@ class BaseImageModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
def create_model_slug(sender, instance, **kwargs):
def _generate_charset():
if private:
return generate_charset(private_slug_length)
return generate_charset(slug_length)
if instance.id is None:
model = sender
slug_length = 5
private_slug_length = 20
private = False
if hasattr(model, "SlugMeta"):
if hasattr(model.SlugMeta, "slug_length"):
slug_length = model.SlugMeta.slug_length
if hasattr(model.SlugMeta, "private_slug_length"):
private_slug_length = model.SlugMeta.private_slug_length
if hasattr(instance, "private"):
if instance.private:
private = True
if hasattr(instance, "public"):
if not instance.public:
private = True
slug = _generate_charset()
while model.objects.filter(slug=slug).exists():
slug = _generate_charset()
instance.slug = slug
class SlugModel(models.Model):
"""
model to store and generate slug for model instances
for custom slug length use: slug_length, private_slug_length Meta options
"""
slug = models.SlugField(max_length=20, blank=True, unique=True)
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
models.signals.pre_save.connect(create_model_slug, sender=cls)
class Meta:
abstract = True
class SlugMeta:
slug_length = 5
private_slug_length = 20

View File

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-03-24 08:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("files", "0005_alter_basefile_description_alter_basefile_folder_and_more"),
]
operations = [
migrations.AlterField(
model_name="basefile",
name="slug",
field=models.SlugField(blank=True, max_length=20, unique=True),
),
]

View File

@ -20,7 +20,6 @@ class BaseFile(TimeStampedModel, ShortLink):
name = CharField(max_length=100) name = CharField(max_length=100)
description = TextField(blank=True) description = TextField(blank=True)
slug = SlugField(max_length=20, blank=True)
private = BooleanField(default=True) private = BooleanField(default=True)
user = ForeignKey("users.User", related_name="files", on_delete=CASCADE) user = ForeignKey("users.User", related_name="files", on_delete=CASCADE)

View File

@ -1,20 +0,0 @@
from django.db.models.signals import pre_save
from django.dispatch import receiver
from akarpov.files.models import BaseFile
from akarpov.utils.generators import generate_charset
@receiver(pre_save, sender=BaseFile)
def file_on_save(sender, instance: BaseFile, **kwargs):
if instance.id is None:
if instance.private:
slug = generate_charset(20)
while BaseFile.objects.filter(slug=slug).exists():
slug = generate_charset(20)
else:
slug = generate_charset(5)
while BaseFile.objects.filter(slug=slug).exists():
slug = generate_charset(5)
instance.slug = slug

View File

@ -0,0 +1,352 @@
[
{
"model": "about.project",
"pk": 1,
"fields": {
"name": "Blog app",
"description": "App to create, view, comment and rate posts",
"md": "",
"image": "uploads/images/E7BE3B27-5184-4C34-969D-D05E25DA69BE.jpeg",
"created": "2021-12-15T09:28:22Z",
"modified": "2022-05-17T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-blog-app",
"view_link": "https://akarpov.ru/"
}
},
{
"model": "about.project",
"pk": 2,
"fields": {
"name": "User app",
"description": "App to handle user login, registration, setting and user profile. API also provided",
"md": "",
"image": "uploads/images/A185F924-5FDF-4580-8116-55FD575D273F.jpeg",
"created": "2021-12-15T09:36:34Z",
"modified": "2022-04-22T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-user-app",
"view_link": "https://akarpov.ru/user/"
}
},
{
"model": "about.project",
"pk": 3,
"fields": {
"name": "File share app",
"description": "App to share files",
"md": "- upload files\r\n- manage allowed upload space for user\r\n- delete files\r\n- create groups\r\n- upload files to groups and share them",
"image": "uploads/images/Screenshot_20220513_130820.png",
"created": "2021-12-27T21:00:00Z",
"modified": "2022-05-16T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-fileshare-app",
"view_link": "http://akarpov.ru/file/"
}
},
{
"model": "about.project",
"pk": 4,
"fields": {
"name": "About me app",
"description": "Page to share my links, stack and projects I made",
"md": "",
"image": "uploads/images/Screenshot_20220513_131404.png",
"created": "2022-02-01T21:00:00Z",
"modified": "2022-05-15T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/about-me",
"view_link": "https://akarpov.ru/about"
}
},
{
"model": "about.project",
"pk": 5,
"fields": {
"name": "Image app",
"description": "Share your images",
"md": "- image lazy load\r\n- image optimization on backend\r\n- metadata extractor",
"image": "uploads/images/Screenshot_20220513_131716.png",
"created": "2022-04-18T21:00:00Z",
"modified": "2022-05-10T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-image-app",
"view_link": "https://akarpov.ru/pics/"
}
},
{
"model": "about.project",
"pk": 6,
"fields": {
"name": "Forms generator",
"description": "Site to generate and share forms",
"md": "- modes available for now:\r\n- - Question\r\n- - Number\r\n- - Select",
"image": "uploads/images/Screenshot_20220513_132259.png",
"created": "2022-04-14T21:00:00Z",
"modified": "2022-05-15T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-forms-app",
"view_link": "https://akarpov.ru/forms/"
}
},
{
"model": "about.project",
"pk": 7,
"fields": {
"name": "Music app",
"description": "App to handle music files, converting them to audio tracks",
"md": "- get list of songs\r\n- share song",
"image": "uploads/images/Screenshot_20220513_132850.png",
"created": "2022-02-07T21:00:00Z",
"modified": "2022-04-17T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-music-app",
"view_link": "https://akarpov.ru/music/"
}
},
{
"model": "about.project",
"pk": 8,
"fields": {
"name": "CTF platform",
"description": "Site to view tasks, submit flags, view attempts and start time",
"md": "",
"image": "uploads/images/Screenshot_20220513_133358.png",
"created": "2021-12-22T21:00:00Z",
"modified": "2022-03-15T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/django-ctf-app",
"view_link": "https://akarpov.ru/ctf/"
}
},
{
"model": "about.project",
"pk": 9,
"fields": {
"name": "Flower shop",
"description": "Site to create orders",
"md": "- create category\r\n- create product\r\n- get notification on order create from bot",
"image": "uploads/images/Screenshot_20220513_133815.png",
"created": "2022-04-05T21:00:00Z",
"modified": "2022-05-31T21:00:00Z",
"source_link": "",
"view_link": "https://sharoboom-livny.ru/"
}
},
{
"model": "about.project",
"pk": 10,
"fields": {
"name": "Tender hackathon backend",
"description": "Backend for tender platform",
"md": "- [Tender hack site](https://tenderhack.ru/)\r\n- get data and create it on various endpoints\r\n- connect to session with web sockets",
"image": "uploads/images/Screenshot_20220513_134426.png",
"created": "2022-04-14T21:00:00Z",
"modified": "2022-04-16T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/tender_hack_back",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 11,
"fields": {
"name": "Bot Polling Master",
"description": "A small telegram bot to handle messages from users on several bots, gets data from web server to synchronize with frontend",
"md": "",
"image": "uploads/images/Screenshot_20220513_140011.png",
"created": "2022-03-17T21:00:00Z",
"modified": "2022-03-19T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/bot_polling_master",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 12,
"fields": {
"name": "vkHandler",
"description": "bot to fetch data from vk, get tags and send posts to user subscribed to tags. Notifies user if post contains date in the morning of this day",
"md": "",
"image": "uploads/images/Screenshot_20220513_145339.png",
"created": "2022-02-11T21:00:00Z",
"modified": "2022-02-14T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/vkHandler",
"view_link": "http://t.me/vkHand_bot"
}
},
{
"model": "about.project",
"pk": 13,
"fields": {
"name": "Image API",
"description": "Api to handle and store images. Crop image, provide user session as JWT",
"md": "",
"image": "uploads/images/photo_2022-01-23_23-54-26.jpg",
"created": "2022-01-23T21:00:00Z",
"modified": "2022-01-23T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/image_api",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 14,
"fields": {
"name": "Music server",
"description": "MPD server over http with function to load tracks from YouTube, Yandex music and Spotify with metadata gathering from Spotify and Yandex",
"md": "[Stream utl](http://music.akarpov.ru)",
"image": "uploads/images/Screenshot_20220923_000907.png",
"created": "2022-09-19T21:00:00Z",
"modified": "2022-09-28T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/music_server",
"view_link": "http://music.akarpov.ru"
}
},
{
"model": "about.project",
"pk": 15,
"fields": {
"name": "Block site generator",
"description": "Backend for constructor sites. Create sites, blocks and build block hierarchy",
"md": "",
"image": "uploads/images/photo_2022-08-18_23-00-40.jpg",
"created": "2022-08-12T21:00:00Z",
"modified": "2022-08-21T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/agora_hack_backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 16,
"fields": {
"name": "RPG backend",
"description": "Chess RPG game backend with web sockets, timeouts and API",
"md": "",
"image": "uploads/images/image.png",
"created": "2022-06-03T21:00:00Z",
"modified": "2022-07-31T21:00:00Z",
"source_link": "https://github.com/evgen-app/chess_rpg_backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 17,
"fields": {
"name": "NLP DOC processer",
"description": "Backend for service to process docx, doc and other files to check if it follows standards.",
"md": "",
"image": "uploads/images/photo_2022-08-28_04-13-48.jpg",
"created": "2022-08-25T21:00:00Z",
"modified": "2022-08-27T21:00:00Z",
"source_link": "https://github.com/Ai-hack-MAGNUM-OPUS/backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 18,
"fields": {
"name": "Motivational platform",
"description": "Website to connect workers in teams and reward with cryptocurrency",
"md": "",
"image": "uploads/images/photo_2022-10-09_10-49-55.jpg",
"created": "2022-10-07T21:00:00Z",
"modified": "2022-10-11T21:00:00Z",
"source_link": "https://github.com/more-tech4-magnum-opus",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 19,
"fields": {
"name": "Product search engine",
"description": "API to handle and help search queries",
"md": "",
"image": "uploads/images/photo_2022-10-22_22-40-48.jpg",
"created": "2022-10-20T21:00:00Z",
"modified": "2022-10-22T21:00:00Z",
"source_link": "https://github.com/magnum-opus-tender-hack/backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 20,
"fields": {
"name": "DICOM edit platform",
"description": "complex platform to view dicom files and generate pathologies",
"md": "",
"image": "uploads/images/photo_2022-11-17_23-54-35.jpg",
"created": "2022-10-24T21:00:00Z",
"modified": "2022-11-05T21:00:00Z",
"source_link": "https://github.com/leaders-of-digital-9-task/backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 21,
"fields": {
"name": "Med Manage Platform",
"description": "App with wide functionality to create forms, manage patient info, create appointments, etc",
"md": "",
"image": "uploads/images/photo_2023-02-21_13-15-28.jpg",
"created": "2022-12-08T21:00:00Z",
"modified": "2022-12-08T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/med_backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 22,
"fields": {
"name": "cookiecutter-django",
"description": "My django cookiecutter for fast creation of django projects specified for API development.",
"md": "Features:\r\n\r\n - DRF\r\n - Poetry\r\n - Docker\r\n - CI\r\n - Celery\r\n - Structlog\r\n - Useful functions and auth",
"image": "uploads/images/photo_2023-02-21_13-28-05.jpg",
"created": "2023-01-06T21:00:00Z",
"modified": "2023-03-10T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/cookiecutter-django",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 23,
"fields": {
"name": "Exhauster analytics",
"description": "Ann app to process information from exhausters and try to warn and predict it",
"md": "",
"image": "uploads/images/photo_2023-02-19_07-33-56.jpg",
"created": "2023-02-18T21:00:00Z",
"modified": "2023-02-18T21:00:00Z",
"source_link": "https://github.com/evraz-hack/backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 24,
"fields": {
"name": "Real estate search",
"description": "",
"md": "",
"image": "uploads/images/photo_2023-02-21_13-41-32.jpg",
"created": "2023-02-18T21:00:00Z",
"modified": "2023-02-18T21:00:00Z",
"source_link": "https://github.com/BlackWallTeam/Backend",
"view_link": ""
}
},
{
"model": "about.project",
"pk": 25,
"fields": {
"name": "Podcast poller script",
"description": "Script to load current listening track from yandex music and send it to telegram chat.",
"md": "",
"image": "uploads/images/photo_2023-02-21_13-43-35.jpg",
"created": "2023-02-19T21:00:00Z",
"modified": "2023-02-19T21:00:00Z",
"source_link": "https://github.com/Alexander-D-Karpov/scripts/tree/master/podcasts",
"view_link": "https://t.me/sanspie_podcasts"
}
}
]

View File

@ -0,0 +1,141 @@
{% extends 'base.html' %}
{% block title %}sanspie: akarpov{% endblock title %}
{% block css %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css">
<script src="https://kit.fontawesome.com/87f586cb23.js" crossorigin="anonymous"></script>
<style>
.outer-link {
font-size: 1.5em;
transition: 150ms;
}
.outer-link:hover {
transform: scale(2.5);
transition: 150ms;
}
</style>
{% endblock %}
{% block content %}
<div class="row mb-3 row-cols-1 row-cols-md-5">
<div id="txt" class="col col-md-7 d-flex justify-content-center align-items-center text-center">
<div class="align-items-center">
<h1 class="display-1 text-center">sanspie</h1>
<h5 class="display-6 text-center"></h5>
<div class="list-group align-items-center mb-3">
<div class="list-group-item mt-3 border-0"><i class="fa-solid fa-code"></i> Web developer</div>
<div class="list-group-item mt-1 border-0"><i class="fa-solid fa-terminal"></i> DevOps</div>
<div class="list-group-item mt-1 border-0"><i class="fa-solid fa-flag"></i> CTF player</div>
</div>
<div class="row d-flex justify-content-center">
<div class="text-center col-md-5">
<a href="https://t.me/s4nspie" class="btn outer-link m-1"><i class="fa-brands fa-telegram"></i></a>
<a href="https://github.com/Alexander-D-Karpov" class="btn outer-link m-1"><i class="fa-brands fa-github"></i></a>
<a href="https://vk.com/al.karpov" class="btn outer-link m-1"><i class="fa-brands fa-vk"></i></a>
<a href="https://www.linkedin.com/in/alexandr-karpov-ba8891218" class="btn outer-link m-1"><i class="fa-brands fa-linkedin"></i></a>
<a href="mailto:alexandr.d.karpov@gmail.com" class="btn outer-link m-1"><i class="fa-solid fa-envelope"></i></a>
<a href="https://discord.com/users/SansPie#9074" class="btn outer-link m-1"><i class="fa-brands fa-discord"></i></a>
<a href="https://ctftime.org/user/113621" class="btn outer-link m-1"><i class="fa-solid fa-font-awesome"></i></a>
<a href="https://www.codewars.com/users/Alexander-D-Karpov" class="btn outer-link m-1"><i class="fa-solid fa-laptop-code"></i></a>
<a href="https://last.fm/user/sanspie" class="btn outer-link m-1"><i class="fa-brands fa-lastfm"></i></a>
<a href="https://steamcommunity.com/id/2280666013" class="btn outer-link m-1"><i class="fa-brands fa-steam"></i></a>
</div>
</div>
<br>
<details class="fs-6 text-center">
<summary>My stack/technologies:</summary>
<div class="text-muted">(the higher, the better I know it)</div>
</details>
<div class="d-flex justify-content-center text-center row">
<div class="col-md-5 mb-2">
<i class="devicon-django-plain outer-link m-1"></i>
<i class="devicon-python-plain outer-link m-1"></i>
<i class="devicon-fastapi-plain outer-link m-1"></i>
<i class="devicon-flask-original outer-link m-1"></i>
<i class="devicon-linux-plain outer-link m-1"></i>
<i class="devicon-docker-plain outer-link m-1"></i>
<i class="devicon-javascript-plain outer-link m-1"></i>
<i class="devicon-html5-plain outer-link m-1"></i>
<i class="devicon-css3-plain outer-link m-1"></i>
<i class="devicon-bootstrap-plain outer-link m-1"></i>
<i class="devicon-nginx-original outer-link m-1"></i>
<i class="devicon-git-plain outer-link m-1"></i>
<i class="devicon-react-original outer-link m-1"></i>
<i class="devicon-jquery-plain outer-link m-1"></i>
<i class="devicon-bash-plain outer-link m-1"></i>
<i class="devicon-postgresql-plain outer-link m-1"></i>
<i class="devicon-sqlite-plain outer-link m-1"></i>
<i class="devicon-redis-plain outer-link m-1"></i>
<i class="devicon-uwsgi-plain outer-link m-1"></i>
<i class="devicon-cplusplus-plain outer-link m-1"></i>
<i class="devicon-graphql-plain outer-link m-1"></i>
<i class="devicon-unity-original outer-link m-1"></i>
<i class="devicon-debian-plain outer-link m-1"></i>
<i class="devicon-vim-plain outer-link m-1"></i>
<i class="devicon-ssh-original outer-link m-1"></i>
<i class="devicon-markdown-original outer-link m-1"></i>
<i class="devicon-mysql-plain outer-link m-1"></i>
<i class="devicon-php-plain outer-link m-1"></i>
<i class="devicon-figma-plain outer-link m-1"></i>
<i class="devicon-mongodb-plain outer-link m-1"></i>
<i class="devicon-apache-plain outer-link m-1"></i>
<i class="devicon-go-original-wordmark outer-link m-1"></i>
<i class="devicon-csharp-plain outer-link m-1"></i>
</div>
</div>
<br>
<div class="row justify-content-center">
<div class="fs-6 text-center">More about me:</div>
<p class="text-center mb-2 col col-md-8">I am 17 y. o. Python developer from Russia, specialized in creating Django web apps. I took part in the development of monolith websites on Django, Flask, FastAPI and microservices with Rest and Graphql. Also, I am interested in web app security and Linux servers administration.</p>
</div>
<br>
</div>
</div>
<div id="img" class="col col-md-5">
<img src="http://akarpov.ru/media/uploads/files/qMO4dDfIXP.webp" class="img-fluid" alt="">
</div>
</div>
<script>
let viewport_width = window.innerWidth;
if (viewport_width < 1444) {
img = document.getElementById("img");
img.parentNode.removeChild(img);
text = document.getElementById("txt");
text.className = "d-flex justify-content-center";
text.parentNode.className = "row";
}
</script>
<div id="projects" class="fs-6 text-center">Projects: </div>
<div class="row row-cols-auto row-cols-md-2 g-4 m-2 d-flex d-block d-md-flex">
{% for project in projects %}
<div class="mb-3">
<a style="text-decoration: none; color: black" href="{{ project.get_absolute_url }}">
<div class="card h-100">
<div class="row g-2 gap-2">
<div class="col-auto w-100 col-lg-auto col-md-auto col-sm-auto">
<img style="max-height: 200px; object-fit: cover" alt="post image" src="{{ project.image.url }}" class="img-fluid col-auto card-img rounded-start">
</div>
<div class="col-auto">
<div class="card-body">
<h5 class="card-title m-2">{{ project.name }}</h5>
<p class="card-text m-2 mb-3">{{ project.description|truncatewords:10 }}</p>
<p class="card-text">
{% if project.source_link %}
<a style="text-decoration: none; color: black" class="m-2" href="{{ project.source_link }}"><i class="bi bi-file-earmark-code"></i> Source code</a> {% endif %} {% if project.view_link %}
<a style="text-decoration: none; color: black" class="m-2" href="{{ project.view_link }}"><i class="bi bi-box-arrow-up-right"></i> View project</a> {% endif %}
</p>
<p class="card-text mb-2"><small class="text-muted m-2">Created: {{ project.created|date:"d M Y" }} </small></p>
{% if project.last_updated %}
<p>
<small class="text-muted m-2">Last updated: {{ project.last_updated|date:"d M Y" }} </small>
</p>
{% endif %}
</div>
</div>
</div>
</div>
</a>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -46,8 +46,8 @@
</li> </li>
{% endif %} {% endif %}
<li> <li>
<a href="#" class="text-muted nav-link px-sm-0 px-2"> <a href="{% url 'about:about' %}" class="{% active_link 'about' %} text-muted nav-link px-sm-0 px-2">
<i class="fs-5 bi-table"></i><span class="ms-1 d-none d-sm-inline">Orders</span></a> <i class="fs-5 bi-person"></i><span class="ms-1 d-none d-sm-inline">About me</span></a>
</li> </li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="text-muted nav-link dropdown-toggle px-sm-0 px-1" id="dropdown" data-bs-toggle="dropdown" aria-expanded="false"> <a href="#" class="text-muted nav-link dropdown-toggle px-sm-0 px-1" id="dropdown" data-bs-toggle="dropdown" aria-expanded="false">

View File

@ -3,7 +3,6 @@
from akarpov.common.tasks import crop_model_image from akarpov.common.tasks import crop_model_image
from akarpov.test_platform.models import Form from akarpov.test_platform.models import Form
from akarpov.utils.generators import generate_charset
@receiver(post_save, sender=Form) @receiver(post_save, sender=Form)
@ -22,18 +21,7 @@ def form_on_create(sender, instance: Form, created, **kwargs):
@receiver(pre_save, sender=Form) @receiver(pre_save, sender=Form)
def form_on_save(sender, instance: Form, **kwargs): def form_on_save(sender, instance: Form, **kwargs):
if instance.id is None: if instance.id:
if instance.public:
slug = generate_charset(5)
while Form.objects.filter(slug=slug).exists():
slug = generate_charset(5)
instance.slug = slug
else:
slug = generate_charset(20)
while Form.objects.filter(slug=slug).exists():
slug = generate_charset(20)
instance.slug = slug
else:
previous = Form.objects.get(id=instance.id) previous = Form.objects.get(id=instance.id)
if ( if (
previous.image != instance.image previous.image != instance.image

View File

@ -0,0 +1,29 @@
{% load markdown_extras %}
{% block content %}
<br>
<a class="mx-5" style="text-decoration: none; color: black" href="{% url "about" %}#projects"><i class="bi-arrow-left"></i> back to all projects</a>
<div class="row m-5 mb-5 g-1 row-cols-1 row-cols-md-2">
<div class="col">
<img class="img-fluid" src="{{ project.image.url }}" alt=""/>
</div>
<div class="col text-center d-flex row align-items-center">
<div class="align-items-center m-3">
<div class="align-self-center">
<h1 class="fs-2">{{ project.name }}</h1>
<p class="lead">{{ project.md | markdown | safe }}</p>
<p>{{ project.description }}</p>
<p>Created: {{ project.created|date:"d M Y" }}</p>
<p>
{% if project.source_link %}
<a style="text-decoration: none; color: black" class="m-2" href="{{ project.source_link }}"><i class="bi bi-file-earmark-code"></i> Source code</a>
{% endif %}
{% if project.view_link %}
<a style="text-decoration: none; color: black" class="m-2" href="{{ project.view_link }}"><i class="bi bi-box-arrow-up-right"></i> View project</a>
{% endif %}
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -4,6 +4,8 @@
from django.urls import reverse from django.urls import reverse
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from akarpov.common.models import SlugModel
class Link(TimeStampedModel): class Link(TimeStampedModel):
source = models.URLField(blank=False) source = models.URLField(blank=False)
@ -53,7 +55,7 @@ def create_model_link(sender, instance, created, **kwargs):
instance.save() instance.save()
class ShortLink(models.Model): class ShortLink(SlugModel):
short_link: Link | None = models.ForeignKey( short_link: Link | None = models.ForeignKey(
"shortener.Link", blank=True, null=True, on_delete=models.SET_NULL "shortener.Link", blank=True, null=True, on_delete=models.SET_NULL
) )

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-03-24 08:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("users", "0005_user_short_link"),
]
operations = [
migrations.AddField(
model_name="user",
name="slug",
field=models.SlugField(blank=True, max_length=20, unique=True),
),
]

View File

@ -139,6 +139,7 @@
LOCAL_APPS = [ LOCAL_APPS = [
"akarpov.users", "akarpov.users",
"akarpov.about",
"akarpov.blog", "akarpov.blog",
"akarpov.files", "akarpov.files",
"akarpov.pipeliner", "akarpov.pipeliner",

View File

@ -14,15 +14,13 @@
urlpatterns = [ urlpatterns = [
path("home", TemplateView.as_view(template_name="pages/home.html"), name="home"), path("home", TemplateView.as_view(template_name="pages/home.html"), name="home"),
path(
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
),
path("health/", include("health_check.urls")), path("health/", include("health_check.urls")),
path("cms/", include("cms.urls")), path("cms/", include("cms.urls")),
# Django Admin, use {% url 'admin:index' %} # Django Admin, use {% url 'admin:index' %}
path(settings.ADMIN_URL, admin.site.urls), path(settings.ADMIN_URL, admin.site.urls),
# User management # User management
path("users/", include("akarpov.users.urls", namespace="users")), path("users/", include("akarpov.users.urls", namespace="users")),
path("about/", include("akarpov.about.urls", namespace="about")),
path("files/", include("akarpov.files.urls", namespace="files")), path("files/", include("akarpov.files.urls", namespace="files")),
path("forms/", include("akarpov.test_platform.urls", namespace="forms")), path("forms/", include("akarpov.test_platform.urls", namespace="forms")),
path("tools/", include("akarpov.tools.urls", namespace="tools")), path("tools/", include("akarpov.tools.urls", namespace="tools")),

25
poetry.lock generated
View File

@ -2084,7 +2084,6 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"},
{file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
] ]
@ -2092,6 +2091,21 @@ files = [
six = "*" six = "*"
tornado = {version = "*", markers = "python_version > \"2.7\""} tornado = {version = "*", markers = "python_version > \"2.7\""}
[[package]]
name = "markdown"
version = "3.4.3"
description = "Python implementation of John Gruber's Markdown."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Markdown-3.4.3-py3-none-any.whl", hash = "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2"},
{file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"},
]
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "2.1.2" version = "2.1.2"
@ -2393,13 +2407,6 @@ files = [
{file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
{file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
{file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
{file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"},
{file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"},
{file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"},
{file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"},
{file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"},
{file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"},
{file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"},
{file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
{file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
{file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
@ -3858,4 +3865,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "1050b1b4456061ae0abb960f7c936270dd87f084b0a283fb5cba3dbef4940e1f" content-hash = "f9a99f36ec97a450398a9bf31df6970377ae436a6f3e0500acf0eaf08dc4199f"

View File

@ -71,6 +71,7 @@ drf-chunked-upload = "^0.5.1"
django-active-link = "^0.1.8" django-active-link = "^0.1.8"
channels = "^4.0.0" channels = "^4.0.0"
django-upload-validator = "^1.1.6" django-upload-validator = "^1.1.6"
markdown = "^3.4.3"
[build-system] [build-system]