This commit is contained in:
Tobias Brunner 2025-01-27 15:14:58 +01:00
parent 4c6732f9d0
commit 79a8c6f280
No known key found for this signature in database
13 changed files with 169 additions and 47 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,12 +1,20 @@
from django.contrib import admin
from django.utils.html import format_html
from .models import CloudProvider, Country, ServiceLevel, Service
@admin.register(CloudProvider)
class CloudProviderAdmin(admin.ModelAdmin):
list_display = ("name",)
list_display = ("name", "logo_preview")
search_fields = ("name",)
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"
@admin.register(Country)
class CountryAdmin(admin.ModelAdmin):
@ -22,7 +30,14 @@ class ServiceLevelAdmin(admin.ModelAdmin):
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
list_display = ("name", "cloud_provider", "service_level", "price")
list_display = ("name", "cloud_provider", "service_level", "price", "logo_preview")
list_filter = ("cloud_provider", "service_level", "countries")
search_fields = ("name", "description")
filter_horizontal = ("countries",)
def logo_preview(self, obj):
if obj.logo:
return format_html(
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
)
return "No logo"

View file

@ -0,0 +1,34 @@
# Generated by Django 5.1.5 on 2025-01-27 14:10
import services.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("services", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="cloudprovider",
name="logo",
field=models.ImageField(
blank=True,
null=True,
upload_to="cloud_provider_logos/",
validators=[services.models.validate_image_size],
),
),
migrations.AddField(
model_name="service",
name="logo",
field=models.ImageField(
blank=True,
null=True,
upload_to="service_logos/",
validators=[services.models.validate_image_size],
),
),
]

View file

@ -1,9 +1,22 @@
from django.db import models
from django.core.exceptions import ValidationError
def validate_image_size(value):
filesize = value.size
if filesize > 1 * 1024 * 1024: # 1MB
raise ValidationError("Maximum file size is 1MB")
class CloudProvider(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
logo = models.ImageField(
upload_to="cloud_provider_logos/",
validators=[validate_image_size],
null=True,
blank=True,
)
def __str__(self):
return self.name
@ -37,6 +50,12 @@ class Service(models.Model):
countries = models.ManyToManyField(Country)
price = models.DecimalField(max_digits=10, decimal_places=2)
features = models.TextField()
logo = models.ImageField(
upload_to="service_logos/",
validators=[validate_image_size],
null=True,
blank=True,
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View file

@ -3,18 +3,30 @@
{% block content %}
<div class="card">
<div class="card-body">
<h2 class="card-title">{{ service.name }}</h2>
<h6 class="card-subtitle mb-3 text-muted">{{ service.cloud_provider.name }}</h6>
<div class="d-flex align-items-start mb-4">
{% if service.logo %}
<img src="{{ service.logo.url }}" alt="{{ service.name }} logo" class="me-4" style="max-height: 100px; max-width: 200px; object-fit: contain;">
{% endif %}
<div>
<h2 class="card-title">{{ service.name }}</h2>
<div class="d-flex align-items-center">
{% if service.cloud_provider.logo %}
<img src="{{ service.cloud_provider.logo.url }}" alt="{{ service.cloud_provider.name }} logo" class="me-2" style="max-height: 30px; max-width: 60px; object-fit: contain;">
{% endif %}
<h6 class="card-subtitle text-muted mb-0">{{ service.cloud_provider.name }}</h6>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-8">
<h5>Description</h5>
<p>{{ service.description }}</p>
<h5>Features</h5>
<p>{{ service.features|linebreaks }}</p>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
@ -22,18 +34,18 @@
<p><strong>Price:</strong> ${{ service.price }}</p>
<p><strong>Service Level:</strong> {{ service.service_level.name }}</p>
<p><strong>Response Time:</strong> {{ service.service_level.response_time }}</p>
<h6>Available Countries</h6>
<ul>
{% for country in service.countries.all %}
<li>{{ country.name }}</li>
<li>{{ country.name }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">Back to Services</a>
</div>
</div>

View file

@ -9,79 +9,89 @@
<form method="get">
<div class="mb-3">
<label for="search" class="form-label">Search</label>
<input type="text" class="form-control" id="search" name="search"
value="{{ request.GET.search }}">
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}">
</div>
<div class="mb-3">
<label for="cloud_provider" class="form-label">Cloud Provider</label>
<select class="form-select" id="cloud_provider" name="cloud_provider">
<option value="">All Providers</option>
{% for provider in cloud_providers %}
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:"i" %}selected{% endif %}>
{{ provider.name }}
</option>
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:'i' %}selected{% endif %}>
{{ provider.name }}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="country" class="form-label">Country</label>
<select class="form-select" id="country" name="country">
<option value="">All Countries</option>
{% for country in countries %}
<option value="{{ country.id }}" {% if request.GET.country == country.id|stringformat:"i" %}selected{% endif %}>
{{ country.name }}
</option>
<option value="{{ country.id }}" {% if request.GET.country == country.id|stringformat:'i' %}selected{% endif %}>
{{ country.name }}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="service_level" class="form-label">Service Level</label>
<select class="form-select" id="service_level" name="service_level">
<option value="">All Levels</option>
{% for level in service_levels %}
<option value="{{ level.id }}" {% if request.GET.service_level == level.id|stringformat:"i" %}selected{% endif %}>
{{ level.name }}
</option>
<option value="{{ level.id }}" {% if request.GET.service_level == level.id|stringformat:'i' %}selected{% endif %}>
{{ level.name }}
</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Apply Filters</button>
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">Clear</a>
</form>
</div>
</div>
</div>
<div class="col-md-9">
<div class="row row-cols-1 row-cols-md-2 g-4">
{% for service in services %}
<div class="col">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">{{ service.name }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ service.cloud_provider.name }}</h6>
<p class="card-text">{{ service.description|truncatewords:30 }}</p>
<p class="card-text">
<small class="text-muted">
Service Level: {{ service.service_level.name }}<br>
Price: ${{ service.price }}
</small>
</p>
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View
Details</a>
<div class="col">
<div class="card h-100">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
{% if service.logo %}
<img src="{{ service.logo.url }}" alt="{{ service.name }} logo" class="me-3" style="max-height: 50px; max-width: 100px; object-fit: contain;">
{% endif %}
<div>
<h5 class="card-title mb-0">{{ service.name }}</h5>
<div class="d-flex align-items-center mt-2">
{% if service.cloud_provider.logo %}
<img src="{{ service.cloud_provider.logo.url }}" alt="{{ service.cloud_provider.name }} logo" class="me-2" style="max-height: 25px; max-width: 50px; object-fit: contain;">
{% endif %}
<h6 class="card-subtitle text-muted mb-0">{{ service.cloud_provider.name }}</h6>
</div>
</div>
</div>
<p class="card-text">{{ service.description|truncatewords:30 }}</p>
<p class="card-text">
<small class="text-muted">
Service Level: {{ service.service_level.name }}<br>
Price: ${{ service.price }}
</small>
</p>
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View Details</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col">
<div class="alert alert-info">
No services found matching your criteria.
<div class="col">
<div class="alert alert-info">
No services found matching your criteria.
</div>
</div>
</div>
{% endfor %}
</div>
</div>