logos
This commit is contained in:
parent
4c6732f9d0
commit
79a8c6f280
13 changed files with 169 additions and 47 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,4 +8,7 @@ wheels/
|
||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
|
# Project specifics
|
||||||
.env
|
.env
|
||||||
|
hub/db.sqlite3
|
||||||
|
|
BIN
hub/db.sqlite3
BIN
hub/db.sqlite3
Binary file not shown.
BIN
hub/media/cloud_provider_logos/cloudscale.png
Normal file
BIN
hub/media/cloud_provider_logos/cloudscale.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
hub/media/service_logos/postgresql.png
Normal file
BIN
hub/media/service_logos/postgresql.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
hub/media/service_logos/postgresql_Nq7hKyN.png
Normal file
BIN
hub/media/service_logos/postgresql_Nq7hKyN.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
|
@ -1,12 +1,20 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.utils.html import format_html
|
||||||
from .models import CloudProvider, Country, ServiceLevel, Service
|
from .models import CloudProvider, Country, ServiceLevel, Service
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CloudProvider)
|
@admin.register(CloudProvider)
|
||||||
class CloudProviderAdmin(admin.ModelAdmin):
|
class CloudProviderAdmin(admin.ModelAdmin):
|
||||||
list_display = ("name",)
|
list_display = ("name", "logo_preview")
|
||||||
search_fields = ("name",)
|
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)
|
@admin.register(Country)
|
||||||
class CountryAdmin(admin.ModelAdmin):
|
class CountryAdmin(admin.ModelAdmin):
|
||||||
|
@ -22,7 +30,14 @@ class ServiceLevelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(Service)
|
@admin.register(Service)
|
||||||
class ServiceAdmin(admin.ModelAdmin):
|
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")
|
list_filter = ("cloud_provider", "service_level", "countries")
|
||||||
search_fields = ("name", "description")
|
search_fields = ("name", "description")
|
||||||
filter_horizontal = ("countries",)
|
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"
|
||||||
|
|
|
@ -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],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,9 +1,22 @@
|
||||||
from django.db import models
|
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):
|
class CloudProvider(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
logo = models.ImageField(
|
||||||
|
upload_to="cloud_provider_logos/",
|
||||||
|
validators=[validate_image_size],
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -37,6 +50,12 @@ class Service(models.Model):
|
||||||
countries = models.ManyToManyField(Country)
|
countries = models.ManyToManyField(Country)
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
features = models.TextField()
|
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)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,20 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<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>
|
<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-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="row mb-4">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
<form method="get">
|
<form method="get">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="search" class="form-label">Search</label>
|
<label for="search" class="form-label">Search</label>
|
||||||
<input type="text" class="form-control" id="search" name="search"
|
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}">
|
||||||
value="{{ request.GET.search }}">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -18,7 +17,7 @@
|
||||||
<select class="form-select" id="cloud_provider" name="cloud_provider">
|
<select class="form-select" id="cloud_provider" name="cloud_provider">
|
||||||
<option value="">All Providers</option>
|
<option value="">All Providers</option>
|
||||||
{% for provider in cloud_providers %}
|
{% for provider in cloud_providers %}
|
||||||
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:"i" %}selected{% endif %}>
|
<option value="{{ provider.id }}" {% if request.GET.cloud_provider == provider.id|stringformat:'i' %}selected{% endif %}>
|
||||||
{{ provider.name }}
|
{{ provider.name }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -30,7 +29,7 @@
|
||||||
<select class="form-select" id="country" name="country">
|
<select class="form-select" id="country" name="country">
|
||||||
<option value="">All Countries</option>
|
<option value="">All Countries</option>
|
||||||
{% for country in countries %}
|
{% for country in countries %}
|
||||||
<option value="{{ country.id }}" {% if request.GET.country == country.id|stringformat:"i" %}selected{% endif %}>
|
<option value="{{ country.id }}" {% if request.GET.country == country.id|stringformat:'i' %}selected{% endif %}>
|
||||||
{{ country.name }}
|
{{ country.name }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -42,7 +41,7 @@
|
||||||
<select class="form-select" id="service_level" name="service_level">
|
<select class="form-select" id="service_level" name="service_level">
|
||||||
<option value="">All Levels</option>
|
<option value="">All Levels</option>
|
||||||
{% for level in service_levels %}
|
{% for level in service_levels %}
|
||||||
<option value="{{ level.id }}" {% if request.GET.service_level == level.id|stringformat:"i" %}selected{% endif %}>
|
<option value="{{ level.id }}" {% if request.GET.service_level == level.id|stringformat:'i' %}selected{% endif %}>
|
||||||
{{ level.name }}
|
{{ level.name }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -62,8 +61,20 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{ service.name }}</h5>
|
<div class="d-flex align-items-center mb-3">
|
||||||
<h6 class="card-subtitle mb-2 text-muted">{{ service.cloud_provider.name }}</h6>
|
{% 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">{{ service.description|truncatewords:30 }}</p>
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
|
@ -71,8 +82,7 @@
|
||||||
Price: ${{ service.price }}
|
Price: ${{ service.price }}
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View
|
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View Details</a>
|
||||||
Details</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = ["django>=5.1.5", "environs[django]~=14.0"]
|
dependencies = ["django>=5.1.5", "environs[django]~=14.0", "pillow>=11.1.0"]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = ["django-browser-reload~=1.13"]
|
dev = ["django-browser-reload~=1.13"]
|
||||||
|
|
29
uv.lock
generated
29
uv.lock
generated
|
@ -109,6 +109,33 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "11.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -125,6 +152,7 @@ source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "environs", extra = ["django"] },
|
{ name = "environs", extra = ["django"] },
|
||||||
|
{ name = "pillow" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
@ -137,6 +165,7 @@ requires-dist = [
|
||||||
{ name = "django", specifier = ">=5.1.5" },
|
{ name = "django", specifier = ">=5.1.5" },
|
||||||
{ name = "django-browser-reload", marker = "extra == 'dev'", specifier = "~=1.13" },
|
{ name = "django-browser-reload", marker = "extra == 'dev'", specifier = "~=1.13" },
|
||||||
{ name = "environs", extras = ["django"], specifier = "~=14.0" },
|
{ name = "environs", extras = ["django"], specifier = "~=14.0" },
|
||||||
|
{ name = "pillow", specifier = ">=11.1.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue