mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-29 01:53:42 +03:00
added base signal and model for slug generation, updated about app
This commit is contained in:
parent
502703117b
commit
359e2929fc
0
akarpov/about/__init__.py
Normal file
0
akarpov/about/__init__.py
Normal file
5
akarpov/about/admin.py
Normal file
5
akarpov/about/admin.py
Normal 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
7
akarpov/about/apps.py
Normal 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")
|
49
akarpov/about/migrations/0001_initial.py
Normal file
49
akarpov/about/migrations/0001_initial.py
Normal 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",),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
akarpov/about/migrations/__init__.py
Normal file
0
akarpov/about/migrations/__init__.py
Normal file
21
akarpov/about/models.py
Normal file
21
akarpov/about/models.py
Normal 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
10
akarpov/about/urls.py
Normal 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
26
akarpov/about/views.py
Normal 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()
|
|
@ -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)
|
||||||
|
|
18
akarpov/blog/migrations/0005_alter_post_slug.py
Normal file
18
akarpov/blog/migrations/0005_alter_post_slug.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
0
akarpov/common/signals.py
Normal file
0
akarpov/common/signals.py
Normal file
18
akarpov/files/migrations/0006_alter_basefile_slug.py
Normal file
18
akarpov/files/migrations/0006_alter_basefile_slug.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
352
akarpov/fixtures/projects.json
Normal file
352
akarpov/fixtures/projects.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
141
akarpov/templates/about/about.html
Normal file
141
akarpov/templates/about/about.html
Normal 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 %}
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
29
akarpov/test_platform/templates/about/project.html
Normal file
29
akarpov/test_platform/templates/about/project.html
Normal 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 %}
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
18
akarpov/users/migrations/0006_user_slug.py
Normal file
18
akarpov/users/migrations/0006_user_slug.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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",
|
||||||
|
|
|
@ -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
25
poetry.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user