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

View File

@ -3,17 +3,12 @@
from akarpov.blog.models import Post, PostRating, Tag
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)
def post_on_save(sender, instance: Post, **kwargs):
if instance.id is None:
slug = generate_charset(3)
while Post.objects.filter(slug=slug).exists():
slug = generate_charset(3)
instance.slug = slug
else:
if instance.id:
previous = Post.objects.get(id=instance.id)
if (
previous.image != instance.image

View File

@ -1,6 +1,7 @@
from django.db import models
from akarpov.utils.files import user_file_upload_mixin
from akarpov.utils.generators import generate_charset
class BaseImageModel(models.Model):
@ -9,3 +10,54 @@ class BaseImageModel(models.Model):
class Meta:
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)
description = TextField(blank=True)
slug = SlugField(max_length=20, blank=True)
private = BooleanField(default=True)
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>
{% endif %}
<li>
<a href="#" class="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>
<a href="{% url 'about:about' %}" class="{% active_link 'about' %} text-muted nav-link px-sm-0 px-2">
<i class="fs-5 bi-person"></i><span class="ms-1 d-none d-sm-inline">About me</span></a>
</li>
<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">

View File

@ -3,7 +3,6 @@
from akarpov.common.tasks import crop_model_image
from akarpov.test_platform.models import Form
from akarpov.utils.generators import generate_charset
@receiver(post_save, sender=Form)
@ -22,18 +21,7 @@ def form_on_create(sender, instance: Form, created, **kwargs):
@receiver(pre_save, sender=Form)
def form_on_save(sender, instance: Form, **kwargs):
if instance.id is None:
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:
if instance.id:
previous = Form.objects.get(id=instance.id)
if (
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 model_utils.models import TimeStampedModel
from akarpov.common.models import SlugModel
class Link(TimeStampedModel):
source = models.URLField(blank=False)
@ -53,7 +55,7 @@ def create_model_link(sender, instance, created, **kwargs):
instance.save()
class ShortLink(models.Model):
class ShortLink(SlugModel):
short_link: Link | None = models.ForeignKey(
"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 = [
"akarpov.users",
"akarpov.about",
"akarpov.blog",
"akarpov.files",
"akarpov.pipeliner",

View File

@ -14,15 +14,13 @@
urlpatterns = [
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("cms/", include("cms.urls")),
# Django Admin, use {% url 'admin:index' %}
path(settings.ADMIN_URL, admin.site.urls),
# User management
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("forms/", include("akarpov.test_platform.urls", namespace="forms")),
path("tools/", include("akarpov.tools.urls", namespace="tools")),

25
poetry.lock generated
View File

@ -2084,7 +2084,6 @@ category = "main"
optional = false
python-versions = "*"
files = [
{file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"},
{file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
]
@ -2092,6 +2091,21 @@ files = [
six = "*"
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]]
name = "markupsafe"
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-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-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_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
{file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
@ -3858,4 +3865,4 @@ files = [
[metadata]
lock-version = "2.0"
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"
channels = "^4.0.0"
django-upload-validator = "^1.1.6"
markdown = "^3.4.3"
[build-system]