diff --git a/config/settings/base.py b/config/settings/base.py index 0d4fb6f..3fcc280 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -13,6 +13,7 @@ ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') INSTALLED_APPS = [ 'jazzmin', + 'modeltranslation', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -117,4 +118,12 @@ EMAIL_HOST = env.str('EMAIL_HOST') EMAIL_PORT = env.str('EMAIL_PORT') EMAIL_USE_TLS = env.str('EMAIL_USE_TLS') -from config.conf import * \ No newline at end of file +from config.conf import * + +MODELTRANSLATION_DEFAULT_LANGUAGE = 'uz' +LANGUAGES = ( + ('uz', 'Uzbek'), + ('en', 'English'), + ('ru', 'Russian'), +) +MODELTRANSLATION_LANGUAGES = ('uz', 'en', 'ru') diff --git a/core/apps/common/admin.py b/core/apps/common/admin.py index 8c38f3f..a78b069 100644 --- a/core/apps/common/admin.py +++ b/core/apps/common/admin.py @@ -1,3 +1,70 @@ -from django.contrib import admin +from django.contrib import admin -# Register your models here. +from django.http import HttpResponseRedirect +from django.urls import reverse +from modeltranslation.admin import TranslationAdmin, TranslationTabularInline + +from core.apps.common import models + + +@admin.register(models.Banner) +class BannerAdmin(admin.ModelAdmin): + list_display = ['banner'] + + +@admin.register(models.SiteConfig) +class SiteConfigAdmin(admin.ModelAdmin): + def has_delete_permission(self, request, obj=...): + return False + + def changelist_view(self, request, extra_context=None): + config, create = models.SiteConfig.objects.get_or_create( + defaults=dict( + telegram='https://t.me/telegram', + facebook='https://facebook.com', + instagram='https://instagram.com', + youtube='https://youtube.com', + ) + ) + url = reverse('admin:common_siteconfig_change', args=[config.id]) + return HttpResponseRedirect(url) + + +class AboutUsImageInline(admin.TabularInline): + model = models.AboutUsImage + extra = 0 + verbose_name = 'rasm' + verbose_name_plural = 'rasmlar' + + +class AboutUsFeatureInline(TranslationTabularInline): + model = models.AboutUsFeature + extra = 0 + verbose_name = 'xususiyat' + verbose_name_plural = 'xususiyatlar' + + +@admin.register(models.AboutUs) +class AboutUsAdmin(TranslationAdmin): + list_display = ['title', 'description'] + inlines = [AboutUsImageInline, AboutUsFeatureInline] + + +@admin.register(models.Service) +class ServiceAdmin(TranslationAdmin): + list_display = ['title', 'text', 'icon', 'image'] + + +@admin.register(models.ContactUs) +class ContactUsAdmin(admin.ModelAdmin): + list_display = [ + 'email', 'phone', 'is_contacted' + ] + + def has_add_permission(self, request): + return False + + +@admin.register(models.News) +class NewsAdmin(TranslationAdmin): + list_display = ['title', 'text', 'image'] \ No newline at end of file diff --git a/core/apps/common/migrations/0001_initial.py b/core/apps/common/migrations/0001_initial.py new file mode 100644 index 0000000..1d2efd0 --- /dev/null +++ b/core/apps/common/migrations/0001_initial.py @@ -0,0 +1,155 @@ +# Generated by Django 5.2 on 2025-08-26 12:25 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='AboutUs', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('title', models.CharField(max_length=200)), + ('title_uz', models.CharField(max_length=200, null=True)), + ('title_en', models.CharField(max_length=200, null=True)), + ('title_ru', models.CharField(max_length=200, null=True)), + ('description', models.TextField()), + ('description_uz', models.TextField(null=True)), + ('description_en', models.TextField(null=True)), + ('description_ru', models.TextField(null=True)), + ], + options={ + 'verbose_name': 'biz haqimizda', + 'verbose_name_plural': 'biz haqimizda', + }, + ), + migrations.CreateModel( + name='Banner', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('banner', models.ImageField(upload_to='banner/images/')), + ], + options={ + 'verbose_name': 'banner', + 'verbose_name_plural': 'bannerlar', + }, + ), + migrations.CreateModel( + name='News', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('image', models.ImageField(unique='news/images/', upload_to='')), + ('title', models.CharField(max_length=300)), + ('title_uz', models.CharField(max_length=300, null=True)), + ('title_en', models.CharField(max_length=300, null=True)), + ('title_ru', models.CharField(max_length=300, null=True)), + ('text', models.TextField()), + ('text_uz', models.TextField(null=True)), + ('text_en', models.TextField(null=True)), + ('text_ru', models.TextField(null=True)), + ], + options={ + 'verbose_name': 'yangilik', + 'verbose_name_plural': 'yangiliklar', + }, + ), + migrations.CreateModel( + name='Service', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('title', models.CharField(max_length=200)), + ('title_uz', models.CharField(max_length=200, null=True)), + ('title_en', models.CharField(max_length=200, null=True)), + ('title_ru', models.CharField(max_length=200, null=True)), + ('text', models.TextField()), + ('text_uz', models.TextField(null=True)), + ('text_en', models.TextField(null=True)), + ('text_ru', models.TextField(null=True)), + ('icon', models.ImageField(upload_to='service/icons/')), + ('image', models.ImageField(upload_to='service/images/')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SiteConfir', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('telegram', models.URLField()), + ('facebook', models.URLField()), + ('youtube', models.URLField()), + ('instagram', models.URLField()), + ], + options={ + 'verbose_name': 'sayt sozlamalari', + 'verbose_name_plural': 'sayt sozlamalari', + }, + ), + migrations.CreateModel( + name='AboutUsFeature', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('text', models.CharField(max_length=200)), + ('text_uz', models.CharField(max_length=200, null=True)), + ('text_en', models.CharField(max_length=200, null=True)), + ('text_ru', models.CharField(max_length=200, null=True)), + ('about_us', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='common.aboutus')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AboutUsImage', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('image', models.ImageField(upload_to='about_us/images/')), + ('about_us', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='common.aboutus')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ContactUs', + fields=[ + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateField(auto_now_add=True)), + ('updated_at', models.DateField(auto_now=True)), + ('first_name', models.CharField(max_length=200)), + ('last_name', models.CharField(max_length=200)), + ('phone', models.CharField(max_length=15)), + ('email', models.EmailField(max_length=254)), + ('message', models.TextField()), + ('is_contacted', models.BooleanField(default=False)), + ('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_us', to='common.service')), + ], + options={ + 'verbose_name': 'ariza', + 'verbose_name_plural': 'arizalar', + }, + ), + ] diff --git a/core/apps/common/migrations/0002_rename_siteconfir_siteconfig.py b/core/apps/common/migrations/0002_rename_siteconfir_siteconfig.py new file mode 100644 index 0000000..a4e1d9d --- /dev/null +++ b/core/apps/common/migrations/0002_rename_siteconfir_siteconfig.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2 on 2025-08-26 12:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0001_initial'), + ] + + operations = [ + migrations.RenameModel( + old_name='SiteConfir', + new_name='SiteConfig', + ), + ] diff --git a/core/apps/common/models.py b/core/apps/common/models.py index 1ae8f1b..c4346ce 100644 --- a/core/apps/common/models.py +++ b/core/apps/common/models.py @@ -8,4 +8,118 @@ class BaseModel(models.Model): updated_at = models.DateField(auto_now=True) class Meta: - abstract = True \ No newline at end of file + abstract = True + + +class Banner(BaseModel): + banner = models.ImageField(upload_to='banner/images/') + + def __str__(self): + return self.banner.name + + def delete(self, *args, **kwargs): + if self.banner: + self.banner.delete(save=False) + super().delete(*args, **kwargs) + + class Meta: + verbose_name = 'banner' + verbose_name_plural = 'bannerlar' + + +class AboutUs(BaseModel): + title = models.CharField(max_length=200) + description = models.TextField() + + def __str__(self): + return self.title + + class Meta: + verbose_name = 'biz haqimizda' + verbose_name_plural = 'biz haqimizda' + + +class AboutUsImage(BaseModel): + image = models.ImageField(upload_to='about_us/images/') + about_us = models.ForeignKey(AboutUs, on_delete=models.CASCADE, related_name='images') + + def __str__(self): + return f'{self.about_us} image {self.image.name}' + + def delete(self, *args, **kwargs): + if self.image: + self.image.delete(save=False) + return super().delete(*args, **kwargs) + + +class AboutUsFeature(BaseModel): + about_us = models.ForeignKey(AboutUs, on_delete=models.CASCADE, related_name='features') + text = models.CharField(max_length=200) + + def __str__(self): + return self.text + + +class Service(BaseModel): + title = models.CharField(max_length=200) + text = models.TextField() + icon = models.ImageField(upload_to='service/icons/') + image = models.ImageField(upload_to='service/images/') + + def __str__(self): + return self.title + + def delete(self, *args, **kwargs): + if self.icon: + self.icon.delete(save=False) + if self.image: + self.image.delete(save=False) + return super().delete(*args, **kwargs) + + class Meta: + verbose_name = 'Xizmatlar' + verbose_name_plural = 'Xizmatlarimiz' + + +class ContactUs(BaseModel): + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + phone = models.CharField(max_length=15) + email = models.EmailField() + service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='contract_us') + message = models.TextField() + is_contacted = models.BooleanField(default=False) + + def __str__(self): + return self.email + + class Meta: + verbose_name = 'ariza' + verbose_name_plural = 'arizalar' + + +class News(BaseModel): + image = models.ImageField(unique='news/images/') + title = models.CharField(max_length=300) + text = models.TextField() + + def __str__(self): + return self.title + + class Meta: + verbose_name = 'yangilik' + verbose_name_plural = 'yangiliklar' + + +class SiteConfig(BaseModel): + telegram = models.URLField() + facebook = models.URLField() + youtube = models.URLField() + instagram = models.URLField() + + def __str__(self): + return 'site config' + + class Meta: + verbose_name = 'sayt sozlamalari' + verbose_name_plural = 'sayt sozlamalari' diff --git a/core/apps/common/translation.py b/core/apps/common/translation.py new file mode 100644 index 0000000..6668de0 --- /dev/null +++ b/core/apps/common/translation.py @@ -0,0 +1,31 @@ +from modeltranslation import translator + +from core.apps.common import models + + +@translator.register(models.AboutUs) +class AboutUsTranslation(translator.TranslationOptions): + fields = [ + 'title', 'description', + ] + + +@translator.register(models.AboutUsFeature) +class AboutUsFeatureTranslation(translator.TranslationOptions): + fields = [ + 'text' + ] + + +@translator.register(models.Service) +class ServiceTranslation(translator.TranslationOptions): + fields = [ + 'title', 'text', + ] + + +@translator.register(models.News) +class NewsTranslation(translator.TranslationOptions): + fields = [ + 'title', 'text' + ] diff --git a/requirements.txt b/requirements.txt index 1e35bd6..f9c38eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ Django==5.2 django-cacheops==7.2 django-environ==0.12.0 django-jazzmin==3.0.1 +django-modeltranslation==0.19.16 django-redis==6.0.0 djangorestframework==3.16.1 djangorestframework_simplejwt==5.5.1 @@ -34,3 +35,4 @@ uritemplate==4.2.0 uvicorn==0.35.0 vine==5.1.0 wcwidth==0.2.13 +pillow \ No newline at end of file