mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-22 06:16:34 +03:00
added user history
This commit is contained in:
parent
099cca3aab
commit
e3454a142a
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("blog", "0006_post_public"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="comment",
|
||||||
|
options={"ordering": ["-rating", "-created"], "verbose_name": "Comment"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="commentrating",
|
||||||
|
options={"verbose_name": "Comment rating"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="post",
|
||||||
|
options={"ordering": ["-created"], "verbose_name": "Post"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="postrating",
|
||||||
|
options={"verbose_name": "Post rating"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="tag",
|
||||||
|
options={"verbose_name": "Tag"},
|
||||||
|
),
|
||||||
|
]
|
|
@ -5,12 +5,13 @@
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from akarpov.common.models import BaseImageModel
|
from akarpov.common.models import BaseImageModel
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
from akarpov.users.models import User
|
from akarpov.users.models import User
|
||||||
|
from akarpov.users.services.history import UserHistoryModel
|
||||||
from akarpov.utils.string import cleanhtml
|
from akarpov.utils.string import cleanhtml
|
||||||
|
|
||||||
|
|
||||||
class Post(BaseImageModel, ShortLink):
|
class Post(BaseImageModel, ShortLinkModel, UserHistoryModel):
|
||||||
title = models.CharField(max_length=100, blank=False)
|
title = models.CharField(max_length=100, blank=False)
|
||||||
body = RichTextUploadingField(blank=False)
|
body = RichTextUploadingField(blank=False)
|
||||||
|
|
||||||
|
@ -64,21 +65,28 @@ def get_absolute_url(self):
|
||||||
return reverse("blog:post", kwargs={"slug": self.slug})
|
return reverse("blog:post", kwargs={"slug": self.slug})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
verbose_name = "Post"
|
||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
class SlugMeta:
|
class SlugMeta:
|
||||||
slug_length = 3
|
slug_length = 3
|
||||||
|
|
||||||
|
|
||||||
class Tag(models.Model):
|
class Tag(UserHistoryModel):
|
||||||
name = models.CharField(max_length=20, unique=True)
|
name = models.CharField(max_length=20, unique=True)
|
||||||
color = ColorField(blank=True, default="#FF0000")
|
color = ColorField(blank=True, default="#FF0000")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("blog:post_list") + f"?tag={self.name}"
|
||||||
|
|
||||||
class PostRating(models.Model):
|
class Meta:
|
||||||
|
verbose_name = "Tag"
|
||||||
|
|
||||||
|
|
||||||
|
class PostRating(UserHistoryModel):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, related_name="post_ratings"
|
User, on_delete=models.CASCADE, related_name="post_ratings"
|
||||||
)
|
)
|
||||||
|
@ -86,6 +94,9 @@ class PostRating(models.Model):
|
||||||
|
|
||||||
vote_up = models.BooleanField(blank=False)
|
vote_up = models.BooleanField(blank=False)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return self.post.get_absolute_url()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
f"{self.user}'s vote up on {self.post.title}"
|
f"{self.user}'s vote up on {self.post.title}"
|
||||||
|
@ -94,10 +105,11 @@ def __str__(self):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
verbose_name = "Post rating"
|
||||||
unique_together = ["user", "post"]
|
unique_together = ["user", "post"]
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model):
|
class Comment(UserHistoryModel):
|
||||||
parent = models.ForeignKey("self", blank=True, null=True, on_delete=models.CASCADE)
|
parent = models.ForeignKey("self", blank=True, null=True, on_delete=models.CASCADE)
|
||||||
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
|
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
|
||||||
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments")
|
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments")
|
||||||
|
@ -107,14 +119,18 @@ class Comment(models.Model):
|
||||||
|
|
||||||
rating = models.IntegerField(default=0)
|
rating = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return self.post.get_absolute_url() + f"#{self.id}"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.author.username}'s comment on {self.post.title}"
|
return f"{self.author.username}'s comment on {self.post.title}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
verbose_name = "Comment"
|
||||||
ordering = ["-rating", "-created"]
|
ordering = ["-rating", "-created"]
|
||||||
|
|
||||||
|
|
||||||
class CommentRating(models.Model):
|
class CommentRating(UserHistoryModel):
|
||||||
comment = models.ForeignKey(
|
comment = models.ForeignKey(
|
||||||
Comment, on_delete=models.CASCADE, related_name="ratings"
|
Comment, on_delete=models.CASCADE, related_name="ratings"
|
||||||
)
|
)
|
||||||
|
@ -127,5 +143,9 @@ class CommentRating(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user}'s vote up" if self.vote_up else f"{self.user}'s vote down"
|
return f"{self.user}'s vote up" if self.vote_up else f"{self.user}'s vote down"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return self.comment.get_absolute_url()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
verbose_name = "Comment rating"
|
||||||
unique_together = ["comment", "user"]
|
unique_together = ["comment", "user"]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("files", "0023_rename_download_basefileitem_downloads_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="file",
|
||||||
|
options={"verbose_name": "File"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="filereport",
|
||||||
|
options={"verbose_name": "File report"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="folder",
|
||||||
|
options={"verbose_name": "Folder"},
|
||||||
|
),
|
||||||
|
]
|
|
@ -19,7 +19,8 @@
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from akarpov.files.services.files import trash_file_upload, user_unique_file_upload
|
from akarpov.files.services.files import trash_file_upload, user_unique_file_upload
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
|
from akarpov.users.services.history import UserHistoryModel
|
||||||
|
|
||||||
|
|
||||||
class BaseFileItem(PolymorphicModel):
|
class BaseFileItem(PolymorphicModel):
|
||||||
|
@ -59,7 +60,7 @@ def save(self, *args, **kwargs):
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class File(BaseFileItem, TimeStampedModel, ShortLink):
|
class File(BaseFileItem, TimeStampedModel, ShortLinkModel, UserHistoryModel):
|
||||||
"""model to store user's files"""
|
"""model to store user's files"""
|
||||||
|
|
||||||
private = BooleanField(default=True)
|
private = BooleanField(default=True)
|
||||||
|
@ -102,8 +103,11 @@ def get_absolute_url(self):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"file: {self.name}"
|
return f"file: {self.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "File"
|
||||||
|
|
||||||
class Folder(BaseFileItem, ShortLink):
|
|
||||||
|
class Folder(BaseFileItem, ShortLinkModel, UserHistoryModel):
|
||||||
name = CharField(max_length=100)
|
name = CharField(max_length=100)
|
||||||
slug = SlugField(max_length=20, blank=True)
|
slug = SlugField(max_length=20, blank=True)
|
||||||
private = BooleanField(default=True)
|
private = BooleanField(default=True)
|
||||||
|
@ -118,6 +122,9 @@ def get_absolute_url(self):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"folder: {self.name}"
|
return f"folder: {self.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Folder"
|
||||||
|
|
||||||
|
|
||||||
class FileInTrash(TimeStampedModel):
|
class FileInTrash(TimeStampedModel):
|
||||||
name = CharField(max_length=200, blank=True)
|
name = CharField(max_length=200, blank=True)
|
||||||
|
@ -132,5 +139,11 @@ class FileReport(Model):
|
||||||
created = DateTimeField(auto_now_add=True)
|
created = DateTimeField(auto_now_add=True)
|
||||||
file = ForeignKey("files.File", related_name="reports", on_delete=CASCADE)
|
file = ForeignKey("files.File", related_name="reports", on_delete=CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "File report"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return self.file.get_absolute_url()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"report on {self.file}"
|
return f"report on {self.file}"
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("gallery", "0002_image_extra_data_image_image_city_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="collection",
|
||||||
|
options={"verbose_name": "Collection"},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="image",
|
||||||
|
options={"verbose_name": "Image"},
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,11 +4,12 @@
|
||||||
from location_field.models.plain import PlainLocationField
|
from location_field.models.plain import PlainLocationField
|
||||||
|
|
||||||
from akarpov.common.models import BaseImageModel
|
from akarpov.common.models import BaseImageModel
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
|
from akarpov.users.services.history import UserHistoryModel
|
||||||
from akarpov.utils.files import user_file_upload_mixin
|
from akarpov.utils.files import user_file_upload_mixin
|
||||||
|
|
||||||
|
|
||||||
class Collection(TimeStampedModel, ShortLink):
|
class Collection(TimeStampedModel, ShortLinkModel, UserHistoryModel):
|
||||||
name = models.CharField(max_length=250, blank=True)
|
name = models.CharField(max_length=250, blank=True)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
public = models.BooleanField(default=False)
|
public = models.BooleanField(default=False)
|
||||||
|
@ -22,8 +23,11 @@ def get_absolute_url(self):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Collection"
|
||||||
|
|
||||||
class Image(TimeStampedModel, ShortLink, BaseImageModel):
|
|
||||||
|
class Image(TimeStampedModel, ShortLinkModel, BaseImageModel, UserHistoryModel):
|
||||||
collection = models.ForeignKey(
|
collection = models.ForeignKey(
|
||||||
"Collection", related_name="images", on_delete=models.CASCADE
|
"Collection", related_name="images", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
@ -46,8 +50,11 @@ def get_absolute_url(self):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.image.name
|
return self.image.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Image"
|
||||||
|
|
||||||
class Tag(ShortLink):
|
|
||||||
|
class Tag(ShortLinkModel):
|
||||||
name = models.CharField(max_length=255, null=True, blank=True)
|
name = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
class MusicConfig(AppConfig):
|
class MusicConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
verbose_name = "Music"
|
||||||
name = "akarpov.music"
|
name = "akarpov.music"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
|
17
akarpov/music/migrations/0002_alter_playlist_options.py
Normal file
17
akarpov/music/migrations/0002_alter_playlist_options.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("music", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="playlist",
|
||||||
|
options={"verbose_name": "Playlist"},
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,10 +2,11 @@
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from akarpov.common.models import BaseImageModel
|
from akarpov.common.models import BaseImageModel
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
|
from akarpov.users.services.history import UserHistoryModel
|
||||||
|
|
||||||
|
|
||||||
class Author(BaseImageModel, ShortLink):
|
class Author(BaseImageModel, ShortLinkModel):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
link = models.URLField(blank=True)
|
link = models.URLField(blank=True)
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Album(BaseImageModel, ShortLink):
|
class Album(BaseImageModel, ShortLinkModel):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
link = models.URLField(blank=True)
|
link = models.URLField(blank=True)
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Song(BaseImageModel, ShortLink):
|
class Song(BaseImageModel, ShortLinkModel):
|
||||||
link = models.URLField(blank=True)
|
link = models.URLField(blank=True)
|
||||||
length = models.IntegerField(null=True)
|
length = models.IntegerField(null=True)
|
||||||
played = models.IntegerField(default=0)
|
played = models.IntegerField(default=0)
|
||||||
|
@ -50,7 +51,7 @@ class SlugMeta:
|
||||||
slug_length = 10
|
slug_length = 10
|
||||||
|
|
||||||
|
|
||||||
class Playlist(ShortLink):
|
class Playlist(ShortLinkModel, UserHistoryModel):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
private = models.BooleanField(default=False)
|
private = models.BooleanField(default=False)
|
||||||
creator = models.ForeignKey(
|
creator = models.ForeignKey(
|
||||||
|
@ -64,6 +65,9 @@ def get_absolute_url(self):
|
||||||
def get_songs(self):
|
def get_songs(self):
|
||||||
return self.songs.all().values("song")
|
return self.songs.all().values("song")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Playlist"
|
||||||
|
|
||||||
|
|
||||||
class PlaylistSong(models.Model):
|
class PlaylistSong(models.Model):
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
|
|
||||||
|
|
||||||
class PipelinerConfig(AppConfig):
|
class PipelinerConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
verbose_name = "Pipeliner"
|
||||||
name = "akarpov.pipeliner"
|
name = "akarpov.pipeliner"
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
<li><a class="dropdown-item {% active_link 'users:update' %}" href="{% url 'users:update' %}">Settings</a></li>
|
<li><a class="dropdown-item {% active_link 'users:update' %}" href="{% url 'users:update' %}">Settings</a></li>
|
||||||
<li><a class="dropdown-item {% active_link 'users:detail' request.user.username %}" href="{% url 'users:detail' request.user.username %}">Profile</a></li>
|
<li><a class="dropdown-item {% active_link 'users:detail' request.user.username %}" href="{% url 'users:detail' request.user.username %}">Profile</a></li>
|
||||||
<li><a class="dropdown-item {% active_link 'tools:qr:create' %}" href="{% url 'tools:promocodes:activate' %}">Activate promocode</a></li>
|
<li><a class="dropdown-item {% active_link 'tools:qr:create' %}" href="{% url 'tools:promocodes:activate' %}">Activate promocode</a></li>
|
||||||
|
<li><a class="dropdown-item {% active_link 'tools:qr:create' %}" href="{% url 'users:history' %}">History</a></li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
{% for comment in post.get_comments %}
|
{% for comment in post.get_comments %}
|
||||||
<div class="col mb-5">
|
<div class="col mb-5">
|
||||||
<div class="d-flex flex-start">
|
<div class="d-flex flex-start">
|
||||||
|
<a href="#{{ comment.id }}"></a>
|
||||||
{% if comment.author.image_cropped %}
|
{% if comment.author.image_cropped %}
|
||||||
<img class="rounded-circle shadow-1-strong me-3"
|
<img class="rounded-circle shadow-1-strong me-3"
|
||||||
src="{{ comment.author.image_cropped.url }}" alt="avatar" width="65"
|
src="{{ comment.author.image_cropped.url }}" alt="avatar" width="65"
|
||||||
|
@ -109,7 +110,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function addComment(){
|
function addComment(){
|
||||||
console.log("aaaaa")
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
28
akarpov/templates/users/history.html
Normal file
28
akarpov/templates/users/history.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<a href="{% url 'users:history_delete' %}" class="btn btn-danger ms-4 mt-3 mb-3">Delete all history</a>
|
||||||
|
<ul style="list-style: none;">
|
||||||
|
{% for record in userhistory_list %}
|
||||||
|
<li class="mt-1 card border-0">
|
||||||
|
<a href="{{ record.get_link }}" class="stretched-link"></a>
|
||||||
|
{% if record.type == 'note' %}
|
||||||
|
<h4 class="me-5"><i class="bi bi-dot fs-6"></i> {{ record.name }}</h4>
|
||||||
|
{% elif record.type == 'create' %}
|
||||||
|
<h4 class="me-5"><i class="bi bi-plus fs-6"></i> {{ record.name }}</h4>
|
||||||
|
{% elif record.type == 'update' %}
|
||||||
|
<h4 class="me-5"><i class="bi bi-pencil fs-6"></i> {{ record.name }}</h4>
|
||||||
|
{% elif record.type == 'delete' %}
|
||||||
|
<h4 class="me-5"><i class="bi bi-trash3 fs-6"></i> {{ record.name }}</h4>
|
||||||
|
{% elif record.type == 'warning' %}
|
||||||
|
<h4 class="text-bg-warning me-5"><i class="bi bi-exclamation-triangle fs-6"></i> {{ record.name }}</h4>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">{{ record.description }}</div>
|
||||||
|
<div class="col-auto">{{ record.created | time:"H:i"}} {{ record.created | date:"d.m.Y"}}</div>
|
||||||
|
</div>
|
||||||
|
<hr class="ms-1 me-5">
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
17
akarpov/test_platform/migrations/0008_alter_form_options.py
Normal file
17
akarpov/test_platform/migrations/0008_alter_form_options.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("test_platform", "0007_alter_form_short_link"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="form",
|
||||||
|
options={"verbose_name": "Form"},
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,12 +8,13 @@
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from akarpov.common.models import BaseImageModel
|
from akarpov.common.models import BaseImageModel
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
from akarpov.users.models import User
|
from akarpov.users.models import User
|
||||||
|
from akarpov.users.services.history import UserHistoryModel
|
||||||
from akarpov.utils.base import SubclassesMixin
|
from akarpov.utils.base import SubclassesMixin
|
||||||
|
|
||||||
|
|
||||||
class Form(BaseImageModel, ShortLink):
|
class Form(BaseImageModel, ShortLinkModel, UserHistoryModel):
|
||||||
name = models.CharField(max_length=200, blank=False)
|
name = models.CharField(max_length=200, blank=False)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
|
@ -45,6 +46,9 @@ def get_absolute_url(self):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"form: {self.name}"
|
return f"form: {self.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Form"
|
||||||
|
|
||||||
|
|
||||||
class BaseQuestion(PolymorphicModel, SubclassesMixin):
|
class BaseQuestion(PolymorphicModel, SubclassesMixin):
|
||||||
type = "no_type"
|
type = "no_type"
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
|
|
||||||
|
|
||||||
class PromocodesConfig(AppConfig):
|
class PromocodesConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
verbose_name = "Promocodes"
|
||||||
name = "akarpov.tools.promocodes"
|
name = "akarpov.tools.promocodes"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
class Link(TimeStampedModel):
|
class Link(TimeStampedModel):
|
||||||
source = models.URLField(blank=False)
|
source = models.URLField(blank=False)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField(db_index=True)
|
||||||
creator = models.ForeignKey(
|
creator = models.ForeignKey(
|
||||||
"users.User", related_name="links", null=True, on_delete=models.SET_NULL
|
"users.User", related_name="links", null=True, on_delete=models.SET_NULL
|
||||||
)
|
)
|
||||||
|
@ -99,7 +99,7 @@ def update_model_link(sender, instance, **kwargs):
|
||||||
instance.short_link = None
|
instance.short_link = None
|
||||||
|
|
||||||
|
|
||||||
class ShortLink(SlugModel):
|
class ShortLinkModel(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
|
||||||
)
|
)
|
||||||
|
|
67
akarpov/users/migrations/0009_userhistory.py
Normal file
67
akarpov/users/migrations/0009_userhistory.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-01 10:43
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("users", "0008_alter_user_left_file_upload"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserHistory",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("note", "note"),
|
||||||
|
("create", "create"),
|
||||||
|
("update", "update"),
|
||||||
|
("delete", "delete"),
|
||||||
|
("warning", "warning"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=500)),
|
||||||
|
("object_id", models.PositiveIntegerField()),
|
||||||
|
(
|
||||||
|
"content_type",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="history",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["content_type", "object_id"],
|
||||||
|
name="users_userh_content_7e9fa5_idx",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-04 09:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0009_userhistory"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="userhistory",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(blank=True, max_length=500),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userhistory",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(max_length=200),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-04 09:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0010_userhistory_description_alter_userhistory_name"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="userhistory",
|
||||||
|
options={"ordering": ["-created"]},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="userhistory",
|
||||||
|
name="created",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,14 +1,16 @@
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db.models import BigIntegerField, CharField, TextField
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from akarpov.common.models import BaseImageModel
|
from akarpov.common.models import BaseImageModel
|
||||||
from akarpov.tools.shortener.models import ShortLink
|
from akarpov.tools.shortener.models import ShortLinkModel
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser, BaseImageModel, ShortLink):
|
class User(AbstractUser, BaseImageModel, ShortLinkModel):
|
||||||
"""
|
"""
|
||||||
Default custom user model for akarpov.
|
Default custom user model for akarpov.
|
||||||
If adding fields that need to be filled at user signup,
|
If adding fields that need to be filled at user signup,
|
||||||
|
@ -16,13 +18,13 @@ class User(AbstractUser, BaseImageModel, ShortLink):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: First and last name do not cover name patterns around the globe
|
#: First and last name do not cover name patterns around the globe
|
||||||
name = CharField(_("Name of User"), blank=True, max_length=255)
|
name = models.CharField(_("Name of User"), blank=True, max_length=255)
|
||||||
about = TextField(_("Description"), blank=True, max_length=100)
|
about = models.TextField(_("Description"), blank=True, max_length=100)
|
||||||
first_name = None # type: ignore
|
first_name = None # type: ignore
|
||||||
last_name = None # type: ignore
|
last_name = None # type: ignore
|
||||||
|
|
||||||
# files
|
# files
|
||||||
left_file_upload = BigIntegerField(
|
left_file_upload = models.BigIntegerField(
|
||||||
"Left file upload(in bites)", default=0, validators=[MinValueValidator(0)]
|
"Left file upload(in bites)", default=0, validators=[MinValueValidator(0)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,3 +36,33 @@ def get_absolute_url(self):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return reverse("users:detail", kwargs={"username": self.username})
|
return reverse("users:detail", kwargs={"username": self.username})
|
||||||
|
|
||||||
|
|
||||||
|
class UserHistory(models.Model):
|
||||||
|
class RecordType(models.TextChoices):
|
||||||
|
note = "note", "note"
|
||||||
|
create = "create", "create"
|
||||||
|
update = "update", "update"
|
||||||
|
delete = "delete", "delete"
|
||||||
|
warning = "warning", "warning"
|
||||||
|
|
||||||
|
type = models.CharField(choices=RecordType.choices)
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
user = models.ForeignKey("User", related_name="history", on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
description = models.CharField(max_length=500, blank=True)
|
||||||
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
|
object_id = models.PositiveIntegerField()
|
||||||
|
object = GenericForeignKey("content_type", "object_id")
|
||||||
|
|
||||||
|
def get_link(self):
|
||||||
|
if hasattr(self.object, "get_absolute_url"):
|
||||||
|
return self.object.get_absolute_url()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-created"]
|
||||||
|
indexes = [models.Index(fields=["content_type", "object_id"])]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self
|
||||||
|
|
112
akarpov/users/services/history.py
Normal file
112
akarpov/users/services/history.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Model
|
||||||
|
|
||||||
|
from akarpov.users.models import User, UserHistory
|
||||||
|
from akarpov.utils.models import get_app_verbose_name, get_object_name, get_object_user
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_note(user: User, name: str, description: str, obj: Model):
|
||||||
|
UserHistory.objects.create(
|
||||||
|
type=UserHistory.RecordType.note,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_creation_note(user: User, name: str, description: str, obj: Model):
|
||||||
|
UserHistory.objects.create(
|
||||||
|
type=UserHistory.RecordType.create,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_update_note(user: User, name: str, description: str, obj: Model):
|
||||||
|
UserHistory.objects.create(
|
||||||
|
type=UserHistory.RecordType.update,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_delete_note(user: User, name: str, description: str, obj: Model):
|
||||||
|
UserHistory.objects.create(
|
||||||
|
type=UserHistory.RecordType.delete,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_warning_note(user: User, name: str, description: str, obj: Model):
|
||||||
|
# TODO: notify user here
|
||||||
|
UserHistory.objects.create(
|
||||||
|
type=UserHistory.RecordType.warning,
|
||||||
|
user=user,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_creation_note_on_create(sender, instance, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
user = get_object_user(instance)
|
||||||
|
if user:
|
||||||
|
create_history_creation_note(
|
||||||
|
user,
|
||||||
|
get_app_verbose_name(sender._meta.app_label),
|
||||||
|
f"Created {sender._meta.verbose_name.title()} {get_object_name(instance)}",
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_update_note_on_update(sender, instance, **kwargs):
|
||||||
|
if instance.id:
|
||||||
|
user = get_object_user(instance)
|
||||||
|
if not kwargs["update_fields"]:
|
||||||
|
create_history_update_note(
|
||||||
|
user,
|
||||||
|
get_app_verbose_name(sender._meta.app_label),
|
||||||
|
f"Updated {sender._meta.verbose_name.title()} {get_object_name(instance)}",
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_history_delete_note_on_delete(sender, instance, **kwargs):
|
||||||
|
user = get_object_user(instance)
|
||||||
|
create_history_delete_note(
|
||||||
|
user,
|
||||||
|
get_app_verbose_name(sender._meta.app_label),
|
||||||
|
f"Deleted {sender._meta.verbose_name.title()} {get_object_name(instance)}",
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserHistoryModel(models.Model):
|
||||||
|
"""
|
||||||
|
creates user history records on model change
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
models.signals.pre_save.connect(
|
||||||
|
create_history_update_note_on_update, sender=cls
|
||||||
|
)
|
||||||
|
models.signals.post_save.connect(
|
||||||
|
create_history_creation_note_on_create, sender=cls
|
||||||
|
)
|
||||||
|
models.signals.post_delete.connect(
|
||||||
|
create_history_delete_note_on_delete, sender=cls
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
|
@ -1,7 +1,20 @@
|
||||||
|
from allauth.account.signals import (
|
||||||
|
email_added,
|
||||||
|
email_changed,
|
||||||
|
email_removed,
|
||||||
|
password_reset,
|
||||||
|
user_logged_in,
|
||||||
|
)
|
||||||
|
from allauth.socialaccount.signals import social_account_added
|
||||||
|
from django.contrib.auth import user_logged_out
|
||||||
from django.db.models.signals import pre_save
|
from django.db.models.signals import pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from akarpov.users.models import User
|
from akarpov.users.models import User
|
||||||
|
from akarpov.users.services.history import (
|
||||||
|
create_history_note,
|
||||||
|
create_history_warning_note,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=User)
|
@receiver(pre_save, sender=User)
|
||||||
|
@ -9,3 +22,42 @@ def user_create(sender, instance: User, **kwargs):
|
||||||
if instance.id is None:
|
if instance.id is None:
|
||||||
# give user some space on file share on register
|
# give user some space on file share on register
|
||||||
instance.left_file_upload += 100 * 1024 * 1024
|
instance.left_file_upload += 100 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(user_logged_in)
|
||||||
|
def user_logged_in(request, user, **kwargs):
|
||||||
|
create_history_note(user, "User", "log in", user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(user_logged_out)
|
||||||
|
def user_logged_out(request, user, **kwargs):
|
||||||
|
create_history_note(user, "User", "log out", user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(password_reset)
|
||||||
|
def user_password_reset(request, user, **kwargs):
|
||||||
|
create_history_warning_note(user, "User", "password reset", user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(email_changed)
|
||||||
|
def user_email_change(request, user, from_email_address, to_email_address, **kwargs):
|
||||||
|
create_history_warning_note(
|
||||||
|
user, "User", f"user email changed to {to_email_address}", user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(email_added)
|
||||||
|
def user_email_add(request, user, email_address, **kwargs):
|
||||||
|
create_history_warning_note(user, "User", f"email {email_address} added", user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(email_removed)
|
||||||
|
def user_email_remove(request, user, email_address, **kwargs):
|
||||||
|
create_history_warning_note(user, "User", f"email {email_address} removed", user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(social_account_added)
|
||||||
|
def user_account_add(request, sociallogin, **kwargs):
|
||||||
|
create_history_warning_note(
|
||||||
|
request.user, "User", f"added {sociallogin.provider} account", request.user
|
||||||
|
)
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from akarpov.users.views import user_detail_view, user_redirect_view, user_update_view
|
from akarpov.users.views import (
|
||||||
|
user_detail_view,
|
||||||
|
user_history_delete_view,
|
||||||
|
user_history_view,
|
||||||
|
user_redirect_view,
|
||||||
|
user_update_view,
|
||||||
|
)
|
||||||
|
|
||||||
app_name = "users"
|
app_name = "users"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("redirect/", view=user_redirect_view, name="redirect"),
|
path("redirect/", view=user_redirect_view, name="redirect"),
|
||||||
path("update/", view=user_update_view, name="update"),
|
path("update/", view=user_update_view, name="update"),
|
||||||
|
path("history/", view=user_history_view, name="history"),
|
||||||
|
path("history/delete", view=user_history_delete_view, name="history_delete"),
|
||||||
path("<str:username>/", view=user_detail_view, name="detail"),
|
path("<str:username>/", view=user_detail_view, name="detail"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
|
||||||
|
|
||||||
|
from akarpov.users.models import UserHistory
|
||||||
|
from akarpov.users.services.history import create_history_warning_note
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class UserDetailView(DetailView):
|
class UserDetailView(DetailView):
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
slug_field = "username"
|
slug_field = "username"
|
||||||
slug_url_kwarg = "username"
|
slug_url_kwarg = "username"
|
||||||
|
@ -19,7 +21,6 @@ class UserDetailView(DetailView):
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
fields = ["username", "name", "image", "about"]
|
fields = ["username", "name", "image", "about"]
|
||||||
success_message = _("Information successfully updated")
|
success_message = _("Information successfully updated")
|
||||||
|
@ -38,7 +39,6 @@ def get_object(self):
|
||||||
|
|
||||||
|
|
||||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
||||||
|
|
||||||
permanent = False
|
permanent = False
|
||||||
|
|
||||||
def get_redirect_url(self):
|
def get_redirect_url(self):
|
||||||
|
@ -46,3 +46,30 @@ def get_redirect_url(self):
|
||||||
|
|
||||||
|
|
||||||
user_redirect_view = UserRedirectView.as_view()
|
user_redirect_view = UserRedirectView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class UserHistoryListView(LoginRequiredMixin, ListView):
|
||||||
|
model = UserHistory
|
||||||
|
template_name = "users/history.html"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return UserHistory.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
user_history_view = UserHistoryListView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class UserHistoryDeleteView(LoginRequiredMixin, RedirectView):
|
||||||
|
def get_redirect_url(self):
|
||||||
|
q = UserHistory.objects.filter(user=self.request.user).exclude(
|
||||||
|
type=UserHistory.RecordType.warning
|
||||||
|
)
|
||||||
|
if q:
|
||||||
|
q.delete()
|
||||||
|
create_history_warning_note(
|
||||||
|
self.request.user, "History", "Deleted history", self.request.user
|
||||||
|
)
|
||||||
|
return reverse("users:history")
|
||||||
|
|
||||||
|
|
||||||
|
user_history_delete_view = UserHistoryDeleteView.as_view()
|
||||||
|
|
31
akarpov/utils/models.py
Normal file
31
akarpov/utils/models.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.db.models import Model
|
||||||
|
|
||||||
|
from akarpov.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_name(obj: Model) -> str:
|
||||||
|
if hasattr(obj, "title"):
|
||||||
|
return obj.title
|
||||||
|
elif hasattr(obj, "name"):
|
||||||
|
return obj.name
|
||||||
|
elif hasattr(obj, "__str__"):
|
||||||
|
return obj.__str__()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_user(obj: Model) -> User | None:
|
||||||
|
if hasattr(obj, "creator"):
|
||||||
|
return obj.creator
|
||||||
|
elif hasattr(obj, "user"):
|
||||||
|
return obj.user
|
||||||
|
elif hasattr(obj, "owner"):
|
||||||
|
return obj.owner
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_app_verbose_name(app: str) -> str:
|
||||||
|
return apps.get_app_config(app).verbose_name
|
Loading…
Reference in New Issue
Block a user