Skip to content

Commit

Permalink
Merge pull request #64 from JahongirHakimjonov/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
JahongirHakimjonov authored Dec 18, 2024
2 parents 94f154f + 56f868e commit d2f621b
Show file tree
Hide file tree
Showing 25 changed files with 338 additions and 140 deletions.
22 changes: 22 additions & 0 deletions apps/mystories/migrations/0003_notification_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 5.0.8 on 2024-12-18 07:19

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("mystories", "0002_initial"),
]

operations = [
migrations.AddField(
model_name="notification",
name="type",
field=models.CharField(
choices=[("SINGLE", "Single"), ("ALL", "All")],
default="SINGLE",
max_length=10,
verbose_name="Type",
),
),
]
28 changes: 11 additions & 17 deletions apps/mystories/models/notification.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import io

from PIL import Image
from django.core.files.base import ContentFile
from django.db import models
from django.utils.translation import gettext_lazy as _

from apps.shared.models import AbstractBaseModel


class NotificationType(models.TextChoices):
SINGLE = "SINGLE", _("Single")
ALL = "ALL", _("All")


class Notification(AbstractBaseModel):
user = models.ForeignKey(
"users.User",
Expand All @@ -25,6 +26,12 @@ class Notification(AbstractBaseModel):
message = models.TextField(db_index=True, verbose_name=_("Message"))
is_send = models.BooleanField(default=False, verbose_name=_("Is Send"))
is_read = models.BooleanField(default=False, verbose_name=_("Is Read"))
type = models.CharField(
max_length=10,
choices=NotificationType,
default=NotificationType.SINGLE,
verbose_name=_("Type"),
)

class Meta:
db_table = "notifications"
Expand All @@ -34,16 +41,3 @@ class Meta:

def __str__(self):
return self.message

def save(self, *args, **kwargs):
if self.banner:
img = Image.open(self.banner)
if img.format != "WEBP":
img_io = io.BytesIO()
img.save(img_io, format="WEBP", quality=100)
self.banner.save(
f"{self.banner.name.split('.')[0]}.webp",
ContentFile(img_io.getvalue()),
save=False,
)
super().save(*args, **kwargs)
30 changes: 0 additions & 30 deletions apps/mystories/models/posts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import io

from PIL import Image
from django.core.files.base import ContentFile
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _

from apps.shared.models import AbstractBaseModel
Expand Down Expand Up @@ -59,30 +53,6 @@ def increment_views(self):
self.view_count += 1
self.save()

def save(self, *args, **kwargs):
if not self.created_at:
self.created_at = timezone.now()
if not self.slug:
self.slug = slugify(f"{self.title}-{self.created_at.strftime('%Y-%m-%d')}")
unique_slug = self.slug
num = 1
while Post.objects.filter(slug=unique_slug).exists():
unique_slug = f"{self.slug}-{num}"
num += 1
self.slug = unique_slug
if self.banner:
img = Image.open(self.banner)
if img.format != "WEBP":
img_io = io.BytesIO()
img.save(img_io, format="WEBP", quality=100)
self.banner.save(
f"{self.banner.name.split('.')[0]}.webp",
ContentFile(img_io.getvalue()),
save=False,
)
self.slug = self.slug.lower()
super().save(*args, **kwargs)


class Like(AbstractBaseModel):
post = models.ForeignKey(
Expand Down
25 changes: 22 additions & 3 deletions apps/mystories/signals/notification.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import io
import logging

from PIL import Image
from django.core.files.base import ContentFile
from django.db.models.signals import post_save
from django.dispatch import receiver

from apps.mystories.models import Notification
from apps.mystories.tasks import send_notification_task
from apps.mystories.models import Notification, NotificationType
from apps.mystories.tasks import send_notification_task, send_notification_to_all_task

logger = logging.getLogger(__name__)


@receiver(post_save, sender=Notification)
def send_notification(sender, instance, created, **kwargs): # noqa
if created:
if created and instance.type == NotificationType.SINGLE:
send_notification_task.delay(instance.id)
logger.info(f"Notification {instance.title} sent.")
elif created and instance.type == NotificationType.ALL:
send_notification_to_all_task.delay(instance.id)
logger.info(f"Notification {instance.title} sent to all users.")
elif created:
if instance.banner:
img = Image.open(instance.banner)
if img.format != "WEBP":
img_io = io.BytesIO()
img.save(img_io, format="WEBP", quality=100)
instance.banner.save(
f"{instance.banner.name.split('.')[0]}.webp",
ContentFile(img_io.getvalue()),
save=False,
)
instance.save()
39 changes: 39 additions & 0 deletions apps/mystories/signals/posts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import io

from PIL import Image
from django.core.files.base import ContentFile
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.utils import timezone
from django.utils.text import slugify

from apps.mystories.models import Like, Comment, Saved
from apps.mystories.models import Post


@receiver(post_save, sender=Like)
Expand Down Expand Up @@ -41,3 +48,35 @@ def increment_saved_count(sender, instance, created, **kwargs): # noqa
def decrement_saved_count(sender, instance, **kwargs): # noqa
instance.post.saved_count -= 1
instance.post.save()


@receiver(post_save, sender=Post)
def post_save_post(sender, instance, created, **kwargs):
if created:
if not instance.created_at:
instance.created_at = timezone.now()
if not instance.slug:
instance.slug = slugify(
f"{instance.title}-{instance.created_at.strftime('%Y-%m-%d')}"
)
unique_slug = instance.slug
num = 1
while Post.objects.filter(slug=unique_slug).exists():
unique_slug = f"{instance.slug}-{num}"
num += 1
instance.slug = unique_slug
instance.slug = instance.slug.lower()
instance.save()
if instance.pk:
old_instance = Post.objects.get(pk=instance.pk)
if old_instance.banner != instance.banner:
if instance.banner:
img = Image.open(instance.banner)
if img.format != "WEBP":
img_io = io.BytesIO()
img.save(img_io, format="WEBP", quality=100)
instance.banner.save(
f"{instance.banner.name.split('.')[0]}.webp",
ContentFile(img_io.getvalue()),
save=False,
)
35 changes: 34 additions & 1 deletion apps/mystories/tasks/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from firebase_admin import messaging

from apps.mystories.models import Notification
from apps.users.models import ActiveSessions
from apps.users.models import ActiveSessions, User

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -39,3 +39,36 @@ def send_notification_task(notification_id):
logger.info(f"Notification sent to user {notification.user.id}")
except Notification.DoesNotExist:
logger.error(f"Notification with id {notification_id} does not exist")


@shared_task
def send_notification_to_all_task(notification_id):
try:
time.sleep(1)
notification = Notification.objects.get(id=notification_id)
users = User.objects.all()
for user in users:
fcm_tokens = ActiveSessions.objects.filter(
user=user, is_active=True, fcm_token__isnull=False
).values_list("fcm_token", flat=True)
for fcm_token in fcm_tokens:
if fcm_token:
message = messaging.Message(
notification=messaging.Notification(
title=notification.title,
body=notification.message,
image=(
notification.banner.url
if notification.banner
and hasattr(notification.banner, "url")
else None
),
),
token=fcm_token,
)
messaging.send(message)
notification.is_send = True
notification.save()
logger.info("Notification sent to all users")
except Notification.DoesNotExist:
logger.error(f"Notification with id {notification_id} does not exist")
7 changes: 5 additions & 2 deletions apps/mystories/views/posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from drf_spectacular.utils import extend_schema
# from silk.profiling.profiler import silk_profile

from apps.mystories.models import Post
from apps.mystories.serializers.posts import (
PostListSerializer,
Expand Down Expand Up @@ -35,6 +37,7 @@ def get_permissions(self):
def get_queryset(self):
return Post.objects.select_related("author", "theme").prefetch_related("tags")

# @silk_profile()
def get(self, request):
queryset = self.get_queryset()
paginator = self.pagination_class()
Expand Down Expand Up @@ -63,7 +66,7 @@ def get_serializer_class(self):
return PostDetailSerializer
elif self.request.method == "PATCH":
return PostSerializer
return PostSerializer # Ensure a default serializer is returned
return PostSerializer

def get_post(self, pk, user=None):
queryset = Post.objects.select_related("author", "theme").prefetch_related(
Expand All @@ -73,7 +76,7 @@ def get_post(self, pk, user=None):
return get_object_or_404(queryset, pk=pk, author=user)
return get_object_or_404(queryset, pk=pk)

@extend_schema(operation_id="post_detail")
# @silk_profile()
def get(self, request, pk=None):
post = self.get_post(pk)
post.increment_views()
Expand Down
8 changes: 0 additions & 8 deletions apps/shared/middlewares/sessions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from django.http import JsonResponse
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication

from apps.users.models import ActiveSessions
from apps.users.services import RegisterService


class CheckActiveSessionMiddleware(MiddlewareMixin):
Expand All @@ -26,12 +24,6 @@ def process_request(request):
user=user, access_token=token
).first()
if session:
session.last_activity = timezone.now()
session.ip_address = RegisterService.get_client_ip(request)
session.location = RegisterService.get_location(session.ip_address)
session.user_agent = request.META.get(
"HTTP_USER_AGENT", "Unknown User Agent"
)
if fcm_token:
session.fcm_token = fcm_token
session.save()
Expand Down
44 changes: 40 additions & 4 deletions apps/users/admin/user.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin
from unfold.decorators import display
from unfold.forms import AdminPasswordChangeForm, UserCreationForm, UserChangeForm
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from apps.users.models import User
from apps.users.models import User, RoleChoices


@admin.register(User)
class UserAdmin(BaseUserAdmin, ModelAdmin):
change_password_form = AdminPasswordChangeForm
add_form = UserCreationForm
form = UserChangeForm
list_display = ("id", "username", "role", "is_active", "email")
list_display = (
"avatars",
"username",
"show_role_customized_color",
"is_active",
"created_at",
)
search_fields = ("email", "username")
list_filter = ("role", "is_active")
list_editable = ("is_active",)
list_display_links = ("id", "username")
list_display_links = ("avatars", "username")
autocomplete_fields = ("country",)
list_per_page = 50
fieldsets = (
Expand Down Expand Up @@ -57,3 +66,30 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
},
),
)

@display(
description=_("Role"),
ordering="role",
label={
RoleChoices.ADMIN: "success", # green
RoleChoices.MODERATOR: "info", # orange
RoleChoices.USER: "info", # red
},
)
def show_role_customized_color(self, obj):
return obj.role, obj.get_role_display()

@display(header=True, description=_("Avatars"))
def avatars(self, obj):
return [
f"{obj.first_name} {obj.last_name}",
f"ID:{obj.id} - {obj.email}",
"AB",
{
"path": obj.avatar.url if obj.avatar else static("images/avatar.webp"),
"squared": False,
"borderless": True,
"width": 50,
"height": 50,
},
]
Loading

0 comments on commit d2f621b

Please sign in to comment.