feat: Dockerize and complete project
This commit is contained in:
@@ -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")}),
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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()})"
|
||||
@@ -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}")
|
||||
@@ -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"),
|
||||
]
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user