prevent slug = product for product and resource

This commit is contained in:
Nico Schottelius 2025-03-08 12:31:57 +09:00
commit 9dc207ea4c
8 changed files with 117 additions and 5 deletions

View file

@ -0,0 +1,44 @@
* Generally to be done for prod [44%]
** TODO Add description to product
- Maybe markdown later
** TODO Link orders to users
** TODO Maybe i18n on everything
** PROGRESS Re-understand get_context_data
- gets additional context such as other models / related models
- Unsure when the context data is being used.
- Usually used in the Detailview of the thing
- so likely referenced by a template or View
** TODO Prevent a product or resource to be named "product"
Because of initial['product'] = self.kwargs['product']
** DONE Add default values to resources
CLOSED: [2025-03-08 Sat 12:27]
** DONE Re-understand get_form_kwargs
CLOSED: [2025-03-08 Sat 11:48]
- Build the keyword arguments required to instantiate the form.
- Kinda defining the keys needed
https://docs.djangoproject.com/en/5.1/ref/class-based-views/mixins-editing/
** DONE Re-understand get_initial
CLOSED: [2025-03-08 Sat 11:48]
- Kinda getting the (initial) values for the keys
- populates form values
" Retrieve initial data for the form. By default, returns a
copy of initial."
https://docs.djangoproject.com/en/5.1/ref/class-based-views/mixins-editing/
** DONE Why are (one time) prices separate from resources?
CLOSED: [2025-03-08 Sat 11:31]
A: because duration prices need to be separate.
- onetime price seems to be reasonable to be inside resource
- Price per timeframe must be separate
- Thus onetime price was also added separately
* Bills [0%]
** TODO Show bills
** TODO Allow to create / send bills
* OIDC integration [%]
** Write code
** Test with authentik

View file

@ -64,6 +64,7 @@ machine. Use `kubectl get nodes` to verify minikube is up and running.
* resources should have a slug
* can be used as an identifier and non unique names
* Execute collectstatic for docker
* OIDC / use authentik
#### 3.1 (validation release, planned)

View file

@ -12,7 +12,6 @@ class ProductOneTimeOrderForm(forms.Form):
def __init__(self, resources, *args, **kwargs):
super().__init__(*args, **kwargs)
for res in resources:
print(res)
field_name = f"{res.slug}"
self.fields[field_name] = forms.FloatField(
required=True,

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2025-03-08 02:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0005_alter_productorder_timeframe_and_more'),
]
operations = [
migrations.AddField(
model_name='resource',
name='default_value',
field=models.FloatField(blank=True, null=True),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 5.0.2 on 2025-03-08 03:31
import app.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0006_resource_default_value'),
]
operations = [
migrations.AlterField(
model_name='product',
name='slug',
field=models.SlugField(null=True, unique=True, validators=[app.models.validate_name_not_product]),
),
migrations.AlterField(
model_name='resource',
name='slug',
field=models.SlugField(null=True, unique=True, validators=[app.models.validate_name_not_product]),
),
]

View file

@ -3,6 +3,20 @@ from django.contrib.auth import get_user_model
from django.utils import timezone
from django.urls import reverse
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_name_not_product(value):
"""
We want to prevent overriding our own code.
So the hardcoded name "product" may not be used as a product or resource name
"""
if value == "product":
raise ValidationError(
_("%(value)s is not allowed as the name"),
params={"value": value},
)
class Currency(models.Model):
slug = models.SlugField(null=True, unique=True)
@ -56,11 +70,12 @@ class PricePerTime(models.Model):
return f"{self.value}{self.currency.short_name}/{self.timeframe}"
class Resource(models.Model):
slug = models.SlugField(null=True, unique=True) # primary identifier
slug = models.SlugField(null=True, unique=True, validators=[validate_name_not_product]) # primary identifier
name = models.CharField(max_length=128, unique=False) # CPU, RAM
unit = models.CharField(max_length=128) # Count, GB
minimum_units = models.FloatField(null=True, blank=True) # might have min
maximum_units = models.FloatField(null=True, blank=True) # might have max
default_value = models.FloatField(null=True, blank=True) # default value to show
step_size = models.FloatField(default=1) # step size
price_per_time = models.ManyToManyField(PricePerTime, blank=True)
@ -68,6 +83,7 @@ class Resource(models.Model):
null=True, blank=True,
on_delete=models.CASCADE)
def __str__(self):
if self.minimum_units:
minimum = self.minimum_units
@ -89,7 +105,7 @@ class Product(models.Model):
Describes a product a user can buy
"""
slug = models.SlugField(null=True, unique=True)
slug = models.SlugField(null=True, unique=True, validators=[validate_name_not_product])
name = models.CharField(max_length=128, unique=True)
resources = models.ManyToManyField(Resource, blank=True) # List of REQUIRED resources

View file

@ -1,7 +1,7 @@
<h2>Order {{ product }}</h2>
{% if timeframe %}
<p>Timeframe: {{ timeframe }}</p>
<p>Selected timeframe: {{ timeframe }}</p>
{% endif %}
<form method="post" >

View file

@ -29,11 +29,16 @@ class ProductOneTimeOrderView(FormView):
return reverse("order-confirmation")
def get_form_kwargs(self):
"""
Keys for the form
"""
kwargs = super().get_form_kwargs()
# Set the product so the form can retrieve the resources
product = get_object_or_404(Product, slug=self.kwargs['product'])
kwargs['resources'] = product.resources.all()
print(f"kwargs = {kwargs}")
return kwargs
def get_initial(self):
@ -41,10 +46,15 @@ class ProductOneTimeOrderView(FormView):
Initial values for the form
"""
initial = super().get_initial()
initial = super().get_initial()
initial['product'] = self.kwargs['product']
product = get_object_or_404(Product, slug=self.kwargs['product'])
for res in product.resources.all():
if res.default_value:
initial[res.slug] = res.default_value
if 'timeframe' in self.kwargs:
initial['timeframe'] = self.kwargs['timeframe']
return initial