I am trying to serve media files in production using django-storages and a ftp server I am using a shared cpannel for ftp and trying to test it before I upload the code to production, it uploads the files in the server fine but doesn’t provide the download url. I want to access the url to download the uploaded media files in cpannel.
Here’s My Code
Settings.py
import os
from pathlib import Path
from dotenv import load_dotenv
from django.contrib.messages import constants as messages
from django.urls import reverse, reverse_lazy
from django.conf import global_settings
load_dotenv()
# project base dir
BASE_DIR = Path(__file__).resolve().parent.parent
# base url for link checker
SITE_DOMAIN = "127.0.0.1:8000"
# secretkey
SECRET_KEY = os.getenv("SECRET_KEY")
# DEBUG = os.getenv("DEBUG")
DEBUG = False
# allowed hosts
ALLOWED_HOSTS = ["*"]
# Application definition
USER_APPS = [
"apps.user",
"apps.container",
]
THIRD_PARTY = [
"widget_tweaks",
"crispy_forms",
"crispy_tailwind",
"active_link", # highlights active link
"template_partials", # django template partials
"django_htmx", # django htmx integration
"django_filters", # filteriing
"mathfilters", # doing maths in templates
"import_export", # import export
"linkcheck", # checks for broken link
"storages",
]
THEME = [
"theme",
]
INSTALLED_APPS = [
"authtools", # authtools
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.humanize", # humanize
"whitenoise.runserver_nostatic", # whitenoise
"django.contrib.staticfiles",
]
INSTALLED_APPS += THIRD_PARTY
INSTALLED_APPS += USER_APPS
INSTALLED_APPS += THEME
TAILWIND_APP_NAME = "theme"
X_FRAME_OPTIONS = "SAMEORIGIN"
MIDDLEWARE = [
# "debug_toolbar.middleware.DebugToolbarMiddleware", # debug toolbar
# "django_browser_reload.middleware.BrowserReloadMiddleware", # browser-reload middleware
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # whitenoise
"django_htmx.middleware.HtmxMiddleware", # django htmx
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
# "global_login_required.GlobalLoginRequiredMiddleware", ## global login required
]
ROOT_URLCONF = "core.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
os.path.join(BASE_DIR, "core/templates"),
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.request", # django tables 2 context processors
"apps.container.context_processors.current_container", # <-- currently active container
"apps.container.context_processors.user_groups_processor", # <-- current users group
"apps.container.context_processors.list_of_containers", # <-- current users group
],
"builtins": [
"template_partials.templatetags.partials",
],
},
},
]
WSGI_APPLICATION = "core.wsgi.application"
# Database
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = "static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STATICFILES_DIRS = [
BASE_DIR / "static",
]
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# for FTP storages
BASE_URL = "/files/"
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "storages.backends.ftp.FTPStorage",
},
}
FTP_STORAGE_LOCATION = (
"ftp://<user>:<password>@<server>:21"
)
# Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = "authtools.User"
# login and logout url
LOGIN_REDIRECT_URL = reverse_lazy("dashboard")
LOGIN_URL = reverse_lazy("login")
LOGOUT_REDIRECT_URL = reverse_lazy("login")
# crispy forms
CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind"
CRISPY_TEMPLATE_PACK = "tailwind"
# navigation active link
ACTIVE_LINK_CSS_CLASS = "block py-2 pl-3 pr-4 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
# message error css tags
MESSAGE_TAGS = {
messages.SUCCESS: """ <div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
</svg>
<span class="sr-only">Check icon</span>
</div>
""",
messages.WARNING: """
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-700 dark:text-orange-200 ">
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"/>
</svg>
<span class="sr-only">Warning icon</span>
</div>""",
messages.ERROR: """ <div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"/>
</svg>
<span class="sr-only">Error icon</span>
</div>""",
}
Urls.py
"""
URL configuration for core project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
from django.contrib import admin
from apps.container.views import dashboard, redirect_to_dashboard
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("container/", include("apps.container.urls")),
path("dashboard", dashboard, name="dashboard"),
path("", redirect_to_dashboard, name="redirect-to-dashboard"),
# path("admin/linkcheck/", include("linkcheck.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
DEBUG = [
path("__debug__/", include("debug_toolbar.urls")),
]
urlpatterns += DEBUG
Models.py
import json
from storages.backends.ftp import FTPStorage
from django.core.exceptions import ValidationError
from django.db import models
from .constants import PAYMENT_METHODS
fs = FTPStorage()
class Shipper(models.Model):
shipper_name = models.CharField(
verbose_name="Shipper Name",
max_length=255,
)
shipper_address = models.TextField(verbose_name="Shipper Address")
shipper_contact = models.CharField(
verbose_name="Shipper Contact Number",
max_length=255,
)
shipper_email = models.EmailField(
verbose_name="Shipper Email", null=True, blank=True
)
class Meta:
db_table = ""
managed = True
verbose_name = "Shipper"
verbose_name_plural = "Shippers"
def __str__(self):
return self.shipper_name
@property
def get_all_road_containers(self):
road_containers = RoadContainer.objects.filter(
shipper=self, container_number__current=True
)
return road_containers
@property
def get_all_sea_containers(self):
sea_containers = SeaContainer.objects.filter(
shipper=self, container_number__current=True
)
return sea_containers
@property
def get_containers(self):
from itertools import chain
containers = chain(
RoadContainer.objects.filter(shipper=self, container_number__current=True),
SeaContainer.objects.filter(shipper=self, container_number__current=True),
)
return containers
class Bank(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
def __str__(self):
return self.name
class Category(models.Model):
category_name = models.CharField(
verbose_name="Category Name",
max_length=10,
unique=True,
)
class Meta:
db_table = ""
managed = True
verbose_name = "Category"
verbose_name_plural = "Categorys"
def __str__(self):
return self.category_name
class Owner(models.Model):
owner_name = models.CharField(
verbose_name="Owner",
max_length=255,
unique=True,
)
owner_primary_number = models.CharField(
verbose_name="Primary Phone Number",
max_length=10,
unique=True,
)
owner_secondary_number = models.CharField(
verbose_name="Secondary Phone Number",
max_length=10,
unique=True,
blank=True,
null=True,
)
owner_email = models.EmailField(
verbose_name="Email",
unique=True,
)
def __str__(self):
return self.owner_name
class Company(models.Model):
name = models.CharField(
max_length=200,
verbose_name="Company Name",
)
owner = models.ForeignKey(
to=Owner,
on_delete=models.CASCADE,
)
def __str__(self):
return self.name
def get_company_name(self):
return self.name
class ContainerNumber(models.Model):
name = models.CharField(
verbose_name="Container Number", max_length=255, unique=True
)
current = models.BooleanField(verbose_name="Current")
def clean(self):
if self.current:
existing_active_instance = ContainerNumber.objects.filter(
current=True
).exclude(pk=self.pk)
if existing_active_instance.exists():
raise ValidationError(
"There can be only one active Container Number at a time."
)
def save(self, *args, **kwargs):
self.full_clean() # This will trigger the clean method before saving
super().save(*args, **kwargs)
def __str__(self):
return self.name
class LCLimit(models.Model):
bank = models.ForeignKey(Bank, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
limit_amount = models.DecimalField(max_digits=10, decimal_places=2)
expiry_date = models.DateField()
approval_percentage = models.IntegerField()
def __str__(self):
return self.bank.name
def total_value_in_usd(self):
sea_containers = SeaContainer.objects.filter(
bank=self.bank,
company=self.company,
documents_cleared=False,
container_number__current=True,
)
total_value = sum(
container.calculate_value_in_usd() or 0 for container in sea_containers
)
return total_value
class MultiHisabFileField(models.Model):
file = models.FileField(
verbose_name="Multiple Hisab File",
storage=fs,
)
def __str__(self):
return str(self.file.name)
class PartyHisab(models.Model):
party_name = models.CharField(verbose_name="Party Name", max_length=255, null=True)
marka_name = models.CharField(
verbose_name="Party Marka Detail", max_length=255, null=True
)
hisab_date = models.DateField(verbose_name="Hisab Date", max_length=255, null=True)
hisab = models.ManyToManyField(to=MultiHisabFileField, verbose_name="Hisab Files")
remarks = models.TextField(verbose_name="Remarks")
def __str__(self):
return str(self.marka_name)
def get_marka_name_words(self):
if self.marka_name:
try:
marka_name_json = self.marka_name.replace("'", '"')
marka_list = json.loads(marka_name_json)
if not isinstance(marka_list, list):
marka_list = [marka_list]
words = [word.strip() for word in marka_list]
return words
except json.JSONDecodeError:
return []
return []
class Payment(models.Model):
payment_settled = models.BooleanField(default=False)
expiry_date = models.DateField()
def __str__(self):
return f"Payment Settled:{self.payment_settled} - Expiry:{self.expiry_date}"
class RoadContainer(models.Model):
container_number = models.ForeignKey(
to=ContainerNumber,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
company = models.ForeignKey(
to="Company",
on_delete=models.SET_NULL,
null=True,
)
number = models.CharField(
verbose_name="Sea Container Number",
max_length=10,
)
payment_method = models.CharField(
max_length=6,
choices=PAYMENT_METHODS,
default="LC",
)
bill_of_lading = models.CharField(
verbose_name="Bill of Lading", max_length=50, null=True, blank=True
)
bill_of_lading_date = models.DateField(
verbose_name="Bill of Lading Date",
null=True,
blank=True,
)
cartoon_number = models.PositiveIntegerField(
verbose_name="Loaded Cartoon Number",
)
refrence_number = models.CharField(
verbose_name="LC / TT / DAP number",
max_length=30,
unique=True,
null=True,
blank=True,
)
refrence_date = models.DateField(
verbose_name="LC / TT / DAP date",
null=True,
blank=True,
)
proforma_invoice_number = models.CharField(
verbose_name="Proforma Invoice Number",
max_length=30,
unique=True,
)
proforma_invoice_date = models.DateField(
verbose_name="Proforma Invoice Date",
)
commercial_invoice_number = models.CharField(
verbose_name="Commercial Invoice Number",
max_length=30,
unique=True,
null=True,
blank=True,
)
commercial_invoice_date = models.DateField(
verbose_name="Commercial Invoice Date",
null=True,
blank=True,
)
customs_cleared = models.BooleanField(
verbose_name="Customs Clearance Status",
default=False,
# null=True,
blank=True,
)
customs_cleared_date = models.DateField(
verbose_name="Customs Cleared Date",
null=True,
blank=True,
)
margin_percentage = models.PositiveSmallIntegerField(
verbose_name="Margin Percentage",
default="10",
)
margin_amount = models.DecimalField(
verbose_name="Margin Amount",
# default="10",
max_digits=13,
null=True,
decimal_places=2,
)
category = models.ForeignKey(
to=Category,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
proforma_invoice_value = models.DecimalField(
verbose_name="Proforma Invoice Amount",
max_digits=13,
decimal_places=2,
blank=True,
null=True,
)
commercial_invoice_value = models.DecimalField(
verbose_name="Commercial Invoice Amount",
max_digits=13,
decimal_places=2,
blank=True,
null=True,
)
bank = models.ForeignKey(
to=Bank,
verbose_name="Bank",
on_delete=models.SET_NULL,
null=True,
)
shipper = models.ForeignKey(
to=Shipper,
verbose_name="Shipper",
on_delete=models.SET_NULL,
null=True,
related_name="shipper_container",
)
hisab_files = models.FileField(
verbose_name="Select Hisab Files",
null=True,
blank=True,
storage=fs,
)
excel_file = models.FileField(
verbose_name="Excel File",
null=True,
blank=True,
storage=fs,
)
customs_excel_file = models.FileField(
verbose_name="Customs Excel File",
null=True,
blank=True,
storage=fs,
)
insurance_file = models.FileField(
storage=fs,
verbose_name="Insurance File",
null=True,
blank=True,
)
contianer_file = models.FileField(
verbose_name="Container Documents",
null=True,
blank=True,
storage=fs,
)
pragyapan_patra = models.FileField(
verbose_name="Pragyapan Patra",
null=True,
blank=True,
storage=fs,
)
dollar_rate = models.DecimalField(
verbose_name="Dollar Rate",
decimal_places=2,
max_digits=5,
blank=True,
null=True,
)
documents_cleared = models.BooleanField(
verbose_name="Documents Cleared",
default=False,
# null=True,
# blank=True,
)
class Meta:
db_table = ""
managed = True
verbose_name = "Road Container"
verbose_name_plural = "Road Containers"
unique_together = ("container_number", "number", "category")
def calculate_value_in_usd(self):
if self.dollar_rate is not None and self.proforma_invoice_value is not None:
value_in_nrp = self.dollar_rate * self.proforma_invoice_value
return value_in_nrp
return None
def __str__(self):
return f"{self.category} - {self.number}"
def get_container_name(self):
return f"{self.category} - {self.number}"
def get_type(self):
return "road"
class RoadLoadingMarka(models.Model):
road_container = models.ForeignKey(
to=RoadContainer,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
party_name = models.CharField(
verbose_name="Party Name",
max_length=255,
null=True,
)
bilty = models.ImageField(
null=True,
storage=fs,
)
bilty_number = models.CharField(
verbose_name="Bilty Number",
max_length=255,
null=True,
)
bilty_date = models.DateField(
null=True,
)
marka_name = models.CharField(
verbose_name="Marka Name",
max_length=255,
null=True,
)
bilty_cartoon_number = models.PositiveSmallIntegerField(
verbose_name="Cartoon",
null=True,
)
loaded_cartoon_number = models.PositiveSmallIntegerField(
verbose_name="Loaded Cartoon",
null=True,
)
bilty_rmb = models.DecimalField(
verbose_name="RMB",
decimal_places=2,
max_digits=5,
null=True,
)
place = models.CharField(
verbose_name="Bilty Place",
max_length=255,
null=True,
)
transport = models.CharField(
verbose_name="Transport",
max_length=155,
null=True,
)
remarks = models.TextField(
verbose_name="Remarks",
blank=True,
null=True,
)
packing_list = models.ImageField(
null=True,
blank=True,
storage=fs,
)
class SeaContainer(models.Model):
container_number = models.ForeignKey(
to=ContainerNumber,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
company = models.ForeignKey(
to="Company",
on_delete=models.SET_NULL,
null=True,
)
number = models.CharField(
verbose_name="Sea Container Number",
max_length=10,
)
payment_method = models.CharField(
max_length=6,
choices=PAYMENT_METHODS,
default="LC",
)
bill_of_lading = models.CharField(
verbose_name="Bill of Lading", max_length=50, null=True, blank=True
)
bill_of_lading_date = models.DateField(
verbose_name="Bill of Lading Date",
null=True,
blank=True,
)
cartoon_number = models.PositiveIntegerField(
verbose_name="Loaded Cartoon Number",
)
refrence_number = models.CharField(
verbose_name="LC / TT / DAP number",
max_length=30,
unique=True,
null=True,
blank=True,
)
refrence_date = models.DateField(
verbose_name="LC / TT / DAP date",
null=True,
blank=True,
)
proforma_invoice_number = models.CharField(
verbose_name="Proforma Invoice Number",
max_length=30,
unique=True,
)
proforma_invoice_date = models.DateField(
verbose_name="Proforma Invoice Date",
)
commercial_invoice_number = models.CharField(
verbose_name="Commercial Invoice Number",
max_length=30,
unique=True,
null=True,
blank=True,
)
commercial_invoice_date = models.DateField(
verbose_name="Commercial Invoice Date",
null=True,
blank=True,
)
customs_cleared = models.BooleanField(
verbose_name="Customs Clearance Status",
default=False,
# null=True,
blank=True,
)
customs_cleared_date = models.DateField(
verbose_name="Customs Cleared Date",
null=True,
blank=True,
)
margin_percentage = models.PositiveSmallIntegerField(
verbose_name="Margin Percentage",
default="10",
)
margin_amount = models.DecimalField(
verbose_name="Margin Amount",
# default="10",
max_digits=13,
null=True,
decimal_places=2,
)
category = models.ForeignKey(
to=Category,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
proforma_invoice_value = models.DecimalField(
verbose_name="Proforma Invoice Amount",
max_digits=13,
decimal_places=2,
blank=True,
null=True,
)
commercial_invoice_value = models.DecimalField(
verbose_name="Commercial Invoice Amount",
max_digits=13,
decimal_places=2,
blank=True,
null=True,
)
bank = models.ForeignKey(
to=Bank,
verbose_name="Bank",
on_delete=models.SET_NULL,
null=True,
)
shipper = models.ForeignKey(
to=Shipper,
verbose_name="Shipper",
on_delete=models.SET_NULL,
null=True,
)
hisab_files = models.FileField(
verbose_name="Select Hisab Files",
null=True,
blank=True,
storage=fs,
)
excel_file = models.FileField(
verbose_name="Excel File",
null=True,
blank=True,
storage=fs,
)
customs_excel_file = models.FileField(
verbose_name="Customs Excel File",
null=True,
blank=True,
storage=fs,
)
insurance_file = models.FileField(
verbose_name="Insurance File",
null=True,
blank=True,
storage=fs,
)
contianer_file = models.FileField(
verbose_name="Container Documents",
null=True,
blank=True,
storage=fs,
)
pragyapan_patra = models.FileField(
verbose_name="Pragyapan Patra",
null=True,
blank=True,
storage=fs,
)
dollar_rate = models.DecimalField(
verbose_name="Dollar Rate",
decimal_places=2,
max_digits=5,
blank=True,
null=True,
)
documents_cleared = models.BooleanField(
verbose_name="Documents Cleared",
default=False,
)
class Meta:
db_table = ""
managed = True
verbose_name = "SeaContainer"
verbose_name_plural = "SeaContainers"
unique_together = ("container_number", "number", "category")
def calculate_value_in_usd(self):
if self.dollar_rate is not None and self.proforma_invoice_value is not None:
value_in_nrp = self.dollar_rate * self.proforma_invoice_value
return value_in_nrp
return None
def __str__(self):
return f"{self.category} - {self.number}"
def get_container_name(self):
return f"{self.category} - {self.number}"
def get_type(self):
return "sea"
class SeaLoadingMarka(models.Model):
sea_container = models.ForeignKey(
to=SeaContainer,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
party_name = models.CharField(
verbose_name="Party Name", max_length=255, default="Null"
)
marka_name = models.CharField(verbose_name="Marka Name", max_length=255)
cartoon_number = models.PositiveIntegerField(verbose_name="Cartoon")
cbm = models.DecimalField(
verbose_name="CBM",
decimal_places=2,
max_digits=5,
null=True,
default=0.00,
)
# all_sent = models.BooleanField(verbose_name="Sent to Customer", default=False)
# remarks = models.TextField(verbose_name="Sent Remarks")
# chalan_image = models.ImageField(verbose_name="Document Chalan")
def __str__(self):
return f"{self.sea_container} - {self.marka_name}"
*Html File*
<h2 class="text-2xl font-semibold mb-4">Container File</h2>
{% if sea_container.contianer_file %}
<iframe src="{{ sea_container.contianer_file.url }}"
class="w-full h-64"
frameborder="0"></iframe>
<a href="{{ sea_container.contianer_file.url }}"
target="_blank"
class="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900">View</a>
{% if 'add_edit' in groups %}
<a hx-delete="{% url "delete-container-file" sea_container.id %}"
hx-confirm="Sure Want to delete?"
class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">Delete</a>
{% endif %}
{% else %}
<p class="font-semibold mb-4 text-red-500">No Container File Uploaded</p>
{% if 'add_edit' in groups %}
<form id="container-form"
hx-encoding='multipart/form-data'
hx-post="{% url "upload-container-file" sea_container.id %}">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
for="hisab_file">Upload file</label>
<div class="grid grid-cols-2 gap-4">
<div>
<input class="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
id="container_file"
name="container_file"
type="file"
required
accept="image/*,.pdf">
</div>
<div>
<button type="submit"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 inline-flex items-center">
Submit
</button>
</div>
</div>
</form>
{% endif %}
{% endif %}
Screenshots