feat: Dockerize and complete project

This commit is contained in:
mohamad
2026-04-27 17:30:08 +03:30
parent 051148ce78
commit 3c10f5c5ed
62 changed files with 5296 additions and 1 deletions
View File
+27
View File
@@ -0,0 +1,27 @@
from django.contrib import admin
from .models import DownloadItem, FAQEntry
@admin.register(FAQEntry)
class FAQEntryAdmin(admin.ModelAdmin):
list_display = ("question", "order", "is_active", "created_at")
list_filter = ("is_active",)
search_fields = ("question", "answer")
list_editable = ("order", "is_active")
fieldsets = (
(None, {"fields": ("question", "answer")}),
("Settings", {"fields": ("order", "is_active")}),
)
@admin.register(DownloadItem)
class DownloadItemAdmin(admin.ModelAdmin):
list_display = ("name", "platform", "version", "is_active", "order")
list_filter = ("platform", "is_active")
search_fields = ("name", "description")
list_editable = ("order", "is_active")
fieldsets = (
(None, {"fields": ("name", "platform", "version", "download_url", "description")}),
("Settings", {"fields": ("order", "is_active")}),
)
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class PagesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.pages"
verbose_name = "Pages"
+50
View File
@@ -0,0 +1,50 @@
# Generated by Django 5.2.13 on 2026-04-27 09:07
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='DownloadItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('platform', models.CharField(choices=[('windows', 'Windows'), ('macos', 'macOS'), ('linux', 'Linux')], max_length=20)),
('version', models.CharField(max_length=50)),
('download_url', models.URLField()),
('description', models.TextField(blank=True)),
('is_active', models.BooleanField(default=True)),
('order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name': 'Download Item',
'verbose_name_plural': 'Download Items',
'ordering': ['order', 'platform'],
},
),
migrations.CreateModel(
name='FAQEntry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question', models.CharField(max_length=500)),
('answer', models.TextField()),
('order', models.PositiveIntegerField(default=0)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'FAQ Entry',
'verbose_name_plural': 'FAQ Entries',
'ordering': ['order'],
},
),
]
View File
+47
View File
@@ -0,0 +1,47 @@
from django.db import models
class FAQEntry(models.Model):
question = models.CharField(max_length=500)
answer = models.TextField()
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["order"]
verbose_name = "FAQ Entry"
verbose_name_plural = "FAQ Entries"
def __str__(self):
return self.question
class DownloadItem(models.Model):
PLATFORM_WINDOWS = "windows"
PLATFORM_MACOS = "macos"
PLATFORM_LINUX = "linux"
PLATFORM_CHOICES = [
(PLATFORM_WINDOWS, "Windows"),
(PLATFORM_MACOS, "macOS"),
(PLATFORM_LINUX, "Linux"),
]
name = models.CharField(max_length=200)
platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES)
version = models.CharField(max_length=50)
download_url = models.URLField()
description = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["order", "platform"]
verbose_name = "Download Item"
verbose_name_plural = "Download Items"
def __str__(self):
return f"{self.name} ({self.get_platform_display()})"
View File
+114
View File
@@ -0,0 +1,114 @@
from django.test import TestCase
from django.urls import reverse
from apps.pages.models import DownloadItem, FAQEntry
class HomeViewTest(TestCase):
def test_home_returns_200(self):
response = self.client.get(reverse("pages:home"))
self.assertEqual(response.status_code, 200)
def test_home_uses_correct_template(self):
response = self.client.get(reverse("pages:home"))
self.assertTemplateUsed(response, "pages/home.html")
class AboutViewTest(TestCase):
def test_about_returns_200(self):
response = self.client.get(reverse("pages:about"))
self.assertEqual(response.status_code, 200)
def test_about_uses_correct_template(self):
response = self.client.get(reverse("pages:about"))
self.assertTemplateUsed(response, "pages/about.html")
class DownloadsViewTest(TestCase):
def setUp(self):
DownloadItem.objects.create(
name="ViSERA Desktop",
platform="windows",
version="1.0",
download_url="https://example.com/windows",
is_active=True,
)
DownloadItem.objects.create(
name="ViSERA Desktop",
platform="macos",
version="Coming Soon",
download_url="#",
is_active=False,
)
def test_downloads_returns_200(self):
response = self.client.get(reverse("pages:downloads"))
self.assertEqual(response.status_code, 200)
def test_downloads_uses_correct_template(self):
response = self.client.get(reverse("pages:downloads"))
self.assertTemplateUsed(response, "pages/downloads.html")
def test_downloads_context_has_platform_keys(self):
response = self.client.get(reverse("pages:downloads"))
self.assertIn("windows_items", response.context)
self.assertIn("macos_items", response.context)
self.assertIn("linux_items", response.context)
def test_windows_item_in_context(self):
response = self.client.get(reverse("pages:downloads"))
self.assertEqual(response.context["windows_items"].count(), 1)
class FAQViewTest(TestCase):
def setUp(self):
FAQEntry.objects.create(
question="What is the license?",
answer="It is CC BY-NC-SA.",
order=1,
is_active=True,
)
FAQEntry.objects.create(
question="Hidden question",
answer="Hidden answer",
order=2,
is_active=False,
)
def test_faq_returns_200(self):
response = self.client.get(reverse("pages:faq"))
self.assertEqual(response.status_code, 200)
def test_faq_uses_correct_template(self):
response = self.client.get(reverse("pages:faq"))
self.assertTemplateUsed(response, "pages/faq.html")
def test_faq_only_shows_active_entries(self):
response = self.client.get(reverse("pages:faq"))
entries = response.context["faq_entries"]
self.assertEqual(entries.count(), 1)
self.assertEqual(entries.first().question, "What is the license?")
class ContactViewTest(TestCase):
def test_contact_returns_200(self):
response = self.client.get(reverse("pages:contact"))
self.assertEqual(response.status_code, 200)
def test_contact_uses_correct_template(self):
response = self.client.get(reverse("pages:contact"))
self.assertTemplateUsed(response, "pages/contact.html")
class NavigationContextTest(TestCase):
def test_nav_main_products_in_context_on_all_pages(self):
urls = [
reverse("pages:home"),
reverse("pages:about"),
reverse("pages:faq"),
reverse("pages:contact"),
reverse("pages:downloads"),
]
for url in urls:
response = self.client.get(url)
self.assertIn("nav_main_products", response.context, f"Missing nav_main_products at {url}")
+13
View File
@@ -0,0 +1,13 @@
from django.urls import path
from . import views
app_name = "pages"
urlpatterns = [
path("", views.HomeView.as_view(), name="home"),
path("about/", views.AboutView.as_view(), name="about"),
path("downloads/", views.DownloadsView.as_view(), name="downloads"),
path("faq/", views.FAQView.as_view(), name="faq"),
path("contact/", views.ContactView.as_view(), name="contact"),
]
+40
View File
@@ -0,0 +1,40 @@
from django.views.generic import ListView, TemplateView
from .models import DownloadItem, FAQEntry
class HomeView(TemplateView):
template_name = "pages/home.html"
class AboutView(TemplateView):
template_name = "pages/about.html"
class DownloadsView(ListView):
template_name = "pages/downloads.html"
context_object_name = "download_items"
def get_queryset(self):
return DownloadItem.objects.filter(is_active=True).order_by("order", "platform")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
all_items = DownloadItem.objects.order_by("order", "platform")
context["windows_items"] = all_items.filter(
platform=DownloadItem.PLATFORM_WINDOWS
)
context["macos_items"] = all_items.filter(platform=DownloadItem.PLATFORM_MACOS)
context["linux_items"] = all_items.filter(platform=DownloadItem.PLATFORM_LINUX)
return context
class FAQView(ListView):
model = FAQEntry
template_name = "pages/faq.html"
context_object_name = "faq_entries"
queryset = FAQEntry.objects.filter(is_active=True)
class ContactView(TemplateView):
template_name = "pages/contact.html"