diff --git a/.forgejo/workflows/build-deploy-prod.yaml b/.forgejo/workflows/build-deploy-prod.yaml index 87f03ae..369272d 100644 --- a/.forgejo/workflows/build-deploy-prod.yaml +++ b/.forgejo/workflows/build-deploy-prod.yaml @@ -4,13 +4,6 @@ on: push: tags: - "*" - paths: - - "deployment/**" - - "docker/**" - - "src/**" - - "Dockerfile" - - "pyproject.toml" - - "uv.lock" workflow_dispatch: jobs: @@ -51,7 +44,7 @@ jobs: esac - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v5 with: context: . push: true @@ -87,7 +80,7 @@ jobs: esac - name: Deploy to OpenShift - uses: docker://quay.io/appuio/oc:v4.18 + uses: docker://quay.io/appuio/oc:v4.16 with: entrypoint: /bin/bash args: | @@ -104,7 +97,7 @@ jobs: OPENSHIFT_URL: ${{ secrets.OPENSHIFT_URL }} - name: Verify deployment - uses: docker://quay.io/appuio/oc:v4.18 + uses: docker://quay.io/appuio/oc:v4.16 with: entrypoint: /bin/bash args: | diff --git a/.forgejo/workflows/build-deploy-staging.yaml b/.forgejo/workflows/build-deploy-staging.yaml index 5f8aba7..92fa6d2 100644 --- a/.forgejo/workflows/build-deploy-staging.yaml +++ b/.forgejo/workflows/build-deploy-staging.yaml @@ -3,13 +3,6 @@ name: Build and Deploy Staging on: push: branches: [main] - paths: - - "deployment/**" - - "docker/**" - - "src/**" - - "Dockerfile" - - "pyproject.toml" - - "uv.lock" workflow_dispatch: jobs: @@ -35,7 +28,7 @@ jobs: password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v5 with: context: . push: true @@ -56,7 +49,7 @@ jobs: uses: actions/checkout@v4 - name: Deploy to OpenShift - uses: docker://quay.io/appuio/oc:v4.18 + uses: docker://quay.io/appuio/oc:v4.16 with: entrypoint: /bin/bash args: | diff --git a/.forgejo/workflows/docs.yaml b/.forgejo/workflows/docs.yaml index 5a17adc..418bc92 100644 --- a/.forgejo/workflows/docs.yaml +++ b/.forgejo/workflows/docs.yaml @@ -30,7 +30,7 @@ jobs: password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v5 with: context: . file: docs/Dockerfile @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v4 - name: Deploy to OpenShift - uses: docker://quay.io/appuio/oc:v4.18 + uses: docker://quay.io/appuio/oc:v4.16 with: entrypoint: /bin/bash args: | diff --git a/.forgejo/workflows/renovate.yaml b/.forgejo/workflows/renovate.yaml deleted file mode 100644 index 2bb35e8..0000000 --- a/.forgejo/workflows/renovate.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Renovate Dependency Bot - -on: - schedule: - - cron: "0 3 * * *" - workflow_dispatch: - -jobs: - renovate: - runs-on: ubuntu-latest - container: catthehacker/ubuntu:act-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - - - name: Renovate - uses: https://github.com/renovatebot/github-action@v42.0.4 - with: - token: ${{ secrets.RENOVATE_TOKEN }} - env: - LOG_LEVEL: info - RENOVATE_ENDPOINT: ${{ vars.RENOVATE_ENDPOINT }} - RENOVATE_PLATFORM: gitea - RENOVATE_REPOSITORIES: ${{ github.repository }} - RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }} - RENOVATE_GIT_AUTHOR: "Renovate Bot " - RENOVATE_USERNAME: renovate - RENOVATE_ENABLE_PYTHON_TOOL_VERSIONS: true diff --git a/.forgejo/workflows/tests.yaml b/.forgejo/workflows/tests.yaml index 79afa42..b86ba55 100644 --- a/.forgejo/workflows/tests.yaml +++ b/.forgejo/workflows/tests.yaml @@ -2,10 +2,6 @@ name: Tests on: push: - paths: - - "src/**" - - "pyproject.toml" - - "uv.lock" workflow_dispatch: jobs: @@ -21,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Install uv - uses: https://github.com/astral-sh/setup-uv@v6 + uses: https://github.com/astral-sh/setup-uv@v5 - name: Run tests run: uv run --env-file=.env.example pytest diff --git a/deployment/kustomize/base/portal/kustomization.yaml b/deployment/kustomize/base/portal/kustomization.yaml index 8818218..140ec08 100644 --- a/deployment/kustomize/base/portal/kustomization.yaml +++ b/deployment/kustomize/base/portal/kustomization.yaml @@ -2,4 +2,3 @@ resources: - deployment.yaml - service.yaml - cronjob.yaml - - objectstorage.yaml diff --git a/deployment/kustomize/base/portal/objectstorage.yaml b/deployment/kustomize/base/portal/objectstorage.yaml deleted file mode 100644 index 6c6ad65..0000000 --- a/deployment/kustomize/base/portal/objectstorage.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: appcat.vshn.io/v1 -kind: ObjectBucket -metadata: - name: portal-storage -spec: - parameters: - region: lpg - writeConnectionSecretToRef: - name: portal-storage-creds diff --git a/deployment/kustomize/overlays/production/kustomization.yaml b/deployment/kustomize/overlays/production/kustomization.yaml index eb0f847..d746bb1 100644 --- a/deployment/kustomize/overlays/production/kustomization.yaml +++ b/deployment/kustomize/overlays/production/kustomization.yaml @@ -11,4 +11,3 @@ resources: - ingress.yaml patches: - path: portal-deployment.yaml - - path: objectstorage.yaml diff --git a/deployment/kustomize/overlays/production/objectstorage.yaml b/deployment/kustomize/overlays/production/objectstorage.yaml deleted file mode 100644 index af056e9..0000000 --- a/deployment/kustomize/overlays/production/objectstorage.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: appcat.vshn.io/v1 -kind: ObjectBucket -metadata: - name: portal-storage -spec: - parameters: - bucketName: servala-portal-storage-production diff --git a/deployment/kustomize/overlays/staging/kustomization.yaml b/deployment/kustomize/overlays/staging/kustomization.yaml index eb0f847..d746bb1 100644 --- a/deployment/kustomize/overlays/staging/kustomization.yaml +++ b/deployment/kustomize/overlays/staging/kustomization.yaml @@ -11,4 +11,3 @@ resources: - ingress.yaml patches: - path: portal-deployment.yaml - - path: objectstorage.yaml diff --git a/deployment/kustomize/overlays/staging/objectstorage.yaml b/deployment/kustomize/overlays/staging/objectstorage.yaml deleted file mode 100644 index e2d3443..0000000 --- a/deployment/kustomize/overlays/staging/objectstorage.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: appcat.vshn.io/v1 -kind: ObjectBucket -metadata: - name: portal-storage -spec: - parameters: - bucketName: servala-portal-storage-staging diff --git a/pyproject.toml b/pyproject.toml index 7cb0977..740bb70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.12" dependencies = [ "argon2-cffi>=23.1.0", "cryptography>=44.0.2", - "django==5.2.1", + "django==5.2", "django-allauth>=65.5.0", "django-fernet-encrypted-fields>=0.3.0", "django-scopes>=2.0.0", diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 727bdf6..0000000 --- a/renovate.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ], - "packageRules": [ - { - "matchManagers": [ - "github-actions" - ], - "matchFileNames": [ - ".forgejo/workflows/*.yml", - ".forgejo/workflows/*.yaml" - ] - }, - { - "matchManagers": [ - "pep621" - ], - "rangeStrategy": "bump" - }, - { - "matchPackageNames": [ - "python" - ], - "matchManagers": [ - "dockerfile" - ], - "versioning": "docker" - } - ], - "labels": [ - "dependencies" - ], - "lockFileMaintenance": { - "enabled": true, - "schedule": [ - "before 5am on monday" - ] - }, - "prConcurrentLimit": 5, - "branchConcurrentLimit": 10 -} \ No newline at end of file diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 2a90464..c113f78 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -1,7 +1,7 @@ import rules import urlman from django.conf import settings -from django.db import models, transaction +from django.db import models from django.utils.functional import cached_property from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ @@ -9,7 +9,6 @@ from django_scopes import ScopedManager, scopes_disabled from servala.core import rules as perms from servala.core.models.mixins import ServalaModelMixin -from servala.core.odoo import CLIENT class Organization(ServalaModelMixin, models.Model): @@ -126,63 +125,9 @@ class BillingEntity(ServalaModelMixin, models.Model): return self.name @classmethod - @transaction.atomic - def create_from_data(cls, name, odoo_data): - """ - Creates a BillingEntity and corresponding Odoo records. - - This method creates a `res.partner` record in Odoo with `company_type='company'` - for the main company, and another `res.partner` record with `company_type='person'` - and `type='invoice'` (linked via `parent_id` to the first record) for the - invoice address. The IDs of these Odoo records are stored in the BillingEntity. - - Args: - odoo_data (dict): A dictionary containing the data for creating - the BillingEntity and Odoo records. - - Expected keys in `odoo_data`: - - `invoice_street` (str): Street for the invoice address. - - `invoice_street2` (str): Second line of street address for the invoice address. - - `invoice_city` (str): City for the invoice address. - - `invoice_zip` (str): ZIP/Postal code for the invoice address. - - `invoice_country` (int): Odoo `res.country` ID for the invoice address country. - - `invoice_email` (str): Email address for the invoice contact. - - `invoice_phone` (str): Phone number for the invoice contact. - """ - instance = cls.objects.create(name=name) - company_payload = { - "name": odoo_data.get("company_name", name), - "company_type": "company", - } - if vat := odoo_data.get("invoice_vat"): - company_payload["vat"] = vat - company_id = CLIENT.execute("res.partner", "create", [company_payload]) - instance.odoo_company_id = company_id - - invoice_address_payload = { - "name": name, - "company_type": "person", - "type": "invoice", - "parent_id": company_id, - } - invoice_optional_fields = { - "street": odoo_data.get("invoice_street"), - "street2": odoo_data.get("invoice_street2"), - "city": odoo_data.get("invoice_city"), - "zip": odoo_data.get("invoice_zip"), - "country_id": odoo_data.get("invoice_country"), - "email": odoo_data.get("invoice_email"), - } - invoice_address_payload.update( - {k: v for k, v in invoice_optional_fields.items() if v is not None} - ) - - invoice_address_id = CLIENT.execute( - "res.partner", "create", [invoice_address_payload] - ) - instance.odoo_invoice_id = invoice_address_id - - instance.save(update_fields=["odoo_company_id", "odoo_invoice_id"]) + def create_from_data(cls, odoo_data): + instance = BillingEntity.objects.create(name=odoo_data.get("name")) + # TODO implement odoo creation from data return instance @classmethod @@ -192,49 +137,6 @@ class BillingEntity(ServalaModelMixin, models.Model): # return instance pass - @cached_property - def odoo_data(self): - data = { - "company": None, - "invoice_address": None, - } - - company_fields = ["name", "company_type", "vat"] - invoice_address_fields = [ - "name", - "company_type", - "type", - "parent_id", - "street", - "street2", - "city", - "zip", - "country_id", - "email", - ] - - if self.odoo_company_id: - company_records = CLIENT.search_read( - model="res.partner", - domain=[["id", "=", self.odoo_company_id]], - fields=company_fields, - limit=1, - ) - if company_records: - data["company"] = company_records[0] - - if self.odoo_invoice_id: - invoice_address_records = CLIENT.search_read( - model="res.partner", - domain=[["id", "=", self.odoo_invoice_id]], - fields=invoice_address_fields, - limit=1, - ) - if invoice_address_records: - data["invoice_address"] = invoice_address_records[0] - - return data - class OrganizationOrigin(ServalaModelMixin, models.Model): """ diff --git a/src/servala/core/odoo.py b/src/servala/core/odoo.py index 22d45ec..85f0b35 100644 --- a/src/servala/core/odoo.py +++ b/src/servala/core/odoo.py @@ -87,33 +87,6 @@ class OdooClient: CLIENT = OdooClient() -# Odoo countries do not change, so they are fetched once per process -COUNTRIES = [] - - -def get_odoo_countries(): - global COUNTRIES - if COUNTRIES: - return COUNTRIES - - try: - odoo_countries_data = CLIENT.search_read( - model="res.country", domain=[], fields=["id", "name"] - ) - # Format as Django choices: [(value, label), ...] - COUNTRIES = [ - (country["id"], country["name"]) for country in odoo_countries_data - ] - # Sort by country name for better UX in dropdowns - COUNTRIES.sort(key=lambda x: x[1]) - except Exception as e: - # Log the error or handle it as appropriate for your application - # For now, return an empty list or a default if Odoo is unavailable - print(f"Error fetching Odoo countries: {e}") - return [("", "Error fetching countries")] # Or just [] - - return COUNTRIES - def get_invoice_addresses(user): """Used during organization creation: retrieves all invoice diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index b08c2f7..115e574 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -3,7 +3,7 @@ from django.forms import ModelForm from django.utils.translation import gettext_lazy as _ from servala.core.models import Organization -from servala.core.odoo import get_invoice_addresses, get_odoo_countries +from servala.core.odoo import get_invoice_addresses from servala.frontend.forms.mixins import HtmxMixin @@ -28,34 +28,33 @@ class OrganizationCreateForm(OrganizationForm): required=False, ) - # Fields for creating a new billing address in Odoo, prefixed with 'invoice_' - invoice_street = forms.CharField(label=_("Line 1"), required=False, max_length=100) - invoice_street2 = forms.CharField(label=_("Line 2"), required=False, max_length=100) - invoice_city = forms.CharField(label=_("City"), required=False, max_length=100) - invoice_zip = forms.CharField(label=_("Postal Code"), required=False, max_length=20) - invoice_country = forms.ChoiceField( - label=_("Country"), - required=False, - choices=get_odoo_countries(), + # Fields for creating a new billing address in Odoo, prefixed with 'ba_' + ba_name = forms.CharField( + label=_("Contact Person / Company Name"), required=False, max_length=100 ) - invoice_email = forms.EmailField(label=_("Billing Email"), required=False) - invoice_phone = forms.CharField(label=_("Phone"), required=False, max_length=30) - invoice_vat = forms.CharField(label=_("VAT ID"), required=False, max_length=50) + ba_street = forms.CharField(label=_("Street"), required=False, max_length=100) + ba_street2 = forms.CharField( + label=_("Street 2 (Optional)"), required=False, max_length=100 + ) + ba_city = forms.CharField(label=_("City"), required=False, max_length=100) + ba_zip = forms.CharField(label=_("ZIP Code"), required=False, max_length=20) + # For state & country, Odoo uses structured data. For now, text input. + # These will need mapping logic when actual Odoo creation is implemented. + ba_state_name = forms.CharField( + label=_("State / Province"), required=False, max_length=100 + ) + ba_country_name = forms.CharField( + label=_("Country"), required=False, max_length=100 + ) + ba_email = forms.EmailField(label=_("Billing Email"), required=False) + ba_phone = forms.CharField(label=_("Billing Phone"), required=False, max_length=30) + ba_vat = forms.CharField(label=_("VAT ID"), required=False, max_length=50) class Meta(OrganizationForm.Meta): pass def __init__(self, *args, user=None, **kwargs): super().__init__(*args, **kwargs) - - if not self.initial.get("invoice_country"): - default_country_name = "Switzerland" - country_choices = self.fields["invoice_country"].choices - for country_id, country_name_label in country_choices: - if country_name_label == default_country_name: - self.initial["invoice_country"] = country_id - break - self.user = user self.odoo_addresses = get_invoice_addresses(self.user) @@ -75,27 +74,39 @@ class OrganizationCreateForm(OrganizationForm): if not self.is_bound and "billing_processing_choice" not in self.initial: self.fields["billing_processing_choice"].initial = "existing" else: - self.fields.pop("billing_processing_choice") + # No existing Odoo addresses. Force 'new' choice. + self.fields["billing_processing_choice"].choices = [ + ("new", _("Create a new billing address")), + ] + self.fields["billing_processing_choice"].initial = "new" + self.fields["billing_processing_choice"].widget = forms.HiddenInput() self.fields["existing_odoo_address_id"].widget = forms.HiddenInput() def clean(self): cleaned_data = super().clean() choice = cleaned_data.get("billing_processing_choice") - if not choice or choice == "new": + if choice == "new": required_fields = [ - "invoice_street", - "invoice_city", - "invoice_zip", - "invoice_country", - "invoice_email", + "ba_name", + "ba_street", + "ba_city", + "ba_zip", + "ba_state_name", + "ba_country_name", + "ba_email", ] for field_name in required_fields: if not cleaned_data.get(field_name): - self.add_error(field_name, _("This field is required.")) + self.add_error( + field_name, + _( + "This field is required when creating a new billing address." + ), + ) else: existing_id_str = cleaned_data.get("existing_odoo_address_id") if not existing_id_str: self.add_error( - "existing_odoo_address_id", _("Please select an invoice address.") + "existing_odoo_address_id", _("Please select an existing address.") ) return cleaned_data diff --git a/src/servala/frontend/forms/service.py b/src/servala/frontend/forms/service.py index 189f9d5..04fe2df 100644 --- a/src/servala/frontend/forms/service.py +++ b/src/servala/frontend/forms/service.py @@ -1,5 +1,4 @@ from django import forms -from django.db.models import Q from django.utils.translation import gettext_lazy as _ from servala.core.models import ( @@ -19,17 +18,13 @@ class ServiceFilterForm(forms.Form): cloud_provider = forms.ModelChoiceField( queryset=CloudProvider.objects.all(), required=False ) - q = forms.CharField(label=_("Search"), required=False) + q = forms.CharField(required=False) def filter_queryset(self, queryset): if category := self.cleaned_data.get("category"): queryset = queryset.filter(category=category) if cloud_provider := self.cleaned_data.get("cloud_provider"): queryset = queryset.filter(offerings__provider=cloud_provider) - if search := self.cleaned_data.get("q"): - queryset = queryset.filter( - Q(name__icontains=search) | Q(category__name__icontains=search) - ) return queryset diff --git a/src/servala/frontend/templates/frontend/organizations/create.html b/src/servala/frontend/templates/frontend/organizations/create.html index 4fcdc7e..fc0fe58 100644 --- a/src/servala/frontend/templates/frontend/organizations/create.html +++ b/src/servala/frontend/templates/frontend/organizations/create.html @@ -5,75 +5,31 @@ {% translate "Create a new organization" %} {% endblock page_title %} {% endblock html_title %} -{% block content %} -
-
-
-
-
-
- {% include "frontend/forms/errors.html" %} - {% csrf_token %} - {{ form.name.as_field_group }} -
-
+{% block card_content %} + + {% include "frontend/forms/errors.html" %} + {% csrf_token %} +
+
+ {{ form.name.as_field_group }} +
+

{% translate "Billing Information" %}

+ {{ form.billing_processing_choice.as_field_group }} +
{{ form.existing_odoo_address_id.as_field_group }}
+
+ {{ form.ba_name.as_field_group }} + {{ form.ba_street.as_field_group }} + {{ form.ba_street2.as_field_group }} + {{ form.ba_city.as_field_group }} + {{ form.ba_zip.as_field_group }} + {{ form.ba_state_name.as_field_group }} + {{ form.ba_country_name.as_field_group }} + {{ form.ba_email.as_field_group }} + {{ form.ba_phone.as_field_group }} + {{ form.ba_vat.as_field_group }}
-
- {% if form.existing_odoo_address_id and form.existing_odoo_address_id.choices %} -
-
-
-

{% translate "Billing Information" %}

-
-
-
- {{ form.billing_processing_choice.as_field_group }} -
{{ form.existing_odoo_address_id.as_field_group }}
-
-
-
-
- {% endif %} -
-
-
-
-

{% translate "Invoice Address" %}

-
-
-
- {{ form.invoice_vat.as_field_group }} -
- {{ form.invoice_street.as_field_group }} - {{ form.invoice_street2.as_field_group }} -
{{ form.invoice_zip.as_field_group }}
-
{{ form.invoice_city.as_field_group }}
- {{ form.invoice_country.as_field_group }} -
-
-
-
-
-
-
-

{% translate "Invoice Contact" %}

-
-
-
- {{ form.invoice_email.as_field_group }} - {{ form.invoice_phone.as_field_group }} -
-
-
-
-
-
-
-
-
-
- -
+
+
@@ -109,8 +65,8 @@ } else { // No existing addresses found, a new address has to be entered. if (existingSection) existingSection.style.display = 'none' - if (newSection) newSection.style.display = '' // Ensure newSection is not null + newSection.style.display = '' } }); -{% endblock content %} +{% endblock card_content %} diff --git a/src/servala/frontend/templates/frontend/organizations/service_detail.html b/src/servala/frontend/templates/frontend/organizations/service_detail.html index a118539..c3962eb 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_detail.html @@ -28,42 +28,38 @@
-
- {% for offering in service.offerings.all %} -
-
-
- {% if offering.provider.logo %} - {{ offering.provider.name }} - {% endif %} -
-

{{ offering.provider.name }}

-
-
-
- {% if offering.description %} -

{{ offering.description }}

- {% elif offering.provider.description %} -

{{ offering.provider.description }}

- {% endif %} -
- + {% for offering in service.offerings.all %} +
+
+ {% if offering.provider.logo %} + {{ offering.provider.name }} + {% endif %} +
+

{{ offering.provider.name }}

- {% empty %} -
-
-

{% translate "No offerings found." %}

-
+
+ {% if offering.description %} +

{{ offering.description }}

+ {% elif offering.provider.description %} +

{{ offering.provider.description }}

+ {% endif %}
- {% endfor %} -
+ +
+ {% empty %} +
+
+

{% translate "No offerings found." %}

+
+
+ {% endfor %}
{% endblock content %} diff --git a/src/servala/frontend/templates/frontend/organizations/services.html b/src/servala/frontend/templates/frontend/organizations/services.html index b3707e4..3286952 100644 --- a/src/servala/frontend/templates/frontend/organizations/services.html +++ b/src/servala/frontend/templates/frontend/organizations/services.html @@ -16,40 +16,36 @@ -
- {% for service in services %} -
-
-
- {% if service.logo %} - {{ service.name }} - {% endif %} -
-

{{ service.name }}

- {{ service.category }} -
-
-
- {% if service.description %}

{{ service.description }}

{% endif %} -
- + {% for service in services %} +
+
+ {% if service.logo %} + {{ service.name }} + {% endif %} +
+

{{ service.name }}

+ {{ service.category }}
- {% empty %} -
-
-

{% translate "No services found." %}

-
+
+ {% if service.description %}

{{ service.description }}

{% endif %}
- {% endfor %} -
+ +
+ {% empty %} +
+
+

{% translate "No services found." %}

+
+
+ {% endfor %} {% endblock content %} diff --git a/src/servala/frontend/templates/frontend/organizations/update.html b/src/servala/frontend/templates/frontend/organizations/update.html index 384d5a5..0d55f22 100644 --- a/src/servala/frontend/templates/frontend/organizations/update.html +++ b/src/servala/frontend/templates/frontend/organizations/update.html @@ -36,105 +36,26 @@ {% endpartialdef org-name-edit %} -{% block content %} -
-
-
-
-
- - - - - {% partial org-name %} - - - - - - -
- {% translate "Name" %} -
- {% translate "Namespace" %} - -
{{ form.instance.namespace }}
- {% translate "System-generated namespace for Kubernetes resources." %} -
-
-
-
-
- {% if form.instance.billing_entity and form.instance.billing_entity.odoo_data.invoice_address %} -
-
-

{% translate "Billing Address" %}

-
-
-
- {% with odoo_data=form.instance.billing_entity.odoo_data %} -
- - - {% if odoo_data.invoice_address %} - - - - - - - - - - {% if odoo_data.invoice_address.street2 %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - {% endif %} - -
- {% translate "Invoice Contact Name" %} - {{ odoo_data.invoice_address.name|default:"" }}
- {% translate "Street" %} - {{ odoo_data.invoice_address.street|default:"" }}
- {% translate "Street 2" %} - {{ odoo_data.invoice_address.street2 }}
- {% translate "City" %} - {{ odoo_data.invoice_address.city|default:"" }}
- {% translate "ZIP Code" %} - {{ odoo_data.invoice_address.zip|default:"" }}
- {% translate "Country" %} - {{ odoo_data.invoice_address.country_id.1|default:"" }}
- {% translate "VAT ID" %} - {{ odoo_data.company.vat|default:"" }}
- {% translate "Invoice Email" %} - {{ odoo_data.invoice_address.email|default:"" }}
-
- {% endwith %} -
-
-
- {% endif %} -
-{% endblock content %} +{% block card_content %} +
+ + + + + {% partial org-name %} + + + + + + +
+ {% translate "Name" %} +
+ {% translate "Namespace" %} + +
{{ form.instance.namespace }}
+ {% translate "System-generated namespace for Kubernetes resources." %} +
+
+{% endblock card_content %} diff --git a/src/servala/frontend/views/organization.py b/src/servala/frontend/views/organization.py index 834dede..fe0bbb2 100644 --- a/src/servala/frontend/views/organization.py +++ b/src/servala/frontend/views/organization.py @@ -22,14 +22,13 @@ class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView): billing_choice = form.cleaned_data.get("billing_processing_choice") billing_entity = None - if not billing_choice or billing_choice == "new": + if billing_choice == "new": billing_entity = BillingEntity.create_from_data( - form.cleaned_data["name"], { - key: value + key[3:]: value for key, value in form.cleaned_data.items() - if key.startswith("invoice_") - }, + if key.startswith("ba_") + } ) elif odoo_id := form.cleaned_data.get("existing_odoo_address_id"): billing_entity = BillingEntity.objects.filter( diff --git a/src/servala/settings.py b/src/servala/settings.py index 654b039..9db4b4f 100644 --- a/src/servala/settings.py +++ b/src/servala/settings.py @@ -116,10 +116,7 @@ if all( "addressing_style": SERVALA_S3_ADDRESSING_STYLE, "signature_version": SERVALA_S3_SIGNATURE_VERSION, }, - }, - "staticfiles": { - "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", - }, + } } ODOO = { diff --git a/uv.lock b/uv.lock index 7feca76..f2e6662 100644 --- a/uv.lock +++ b/uv.lock @@ -289,16 +289,16 @@ wheels = [ [[package]] name = "django" -version = "5.2.1" +version = "5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/10/0d546258772b8f31398e67c85e52c66ebc2b13a647193c3eef8ee433f1a8/django-5.2.1.tar.gz", hash = "sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284", size = 10818735, upload-time = "2025-05-07T14:06:17.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/1b/c6da718c65228eb3a7ff7ba6a32d8e80fa840ca9057490504e099e4dd1ef/Django-5.2.tar.gz", hash = "sha256:1a47f7a7a3d43ce64570d350e008d2949abe8c7e21737b351b6a1611277c6d89", size = 10824891, upload-time = "2025-04-02T13:08:06.874Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/92/7448697b5838b3a1c6e1d2d6a673e908d0398e84dc4f803a2ce11e7ffc0f/django-5.2.1-py3-none-any.whl", hash = "sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961", size = 8301833, upload-time = "2025-05-07T14:06:10.955Z" }, + { url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361, upload-time = "2025-04-02T13:08:01.465Z" }, ] [[package]] @@ -1022,7 +1022,7 @@ dev = [ requires-dist = [ { name = "argon2-cffi", specifier = ">=23.1.0" }, { name = "cryptography", specifier = ">=44.0.2" }, - { name = "django", specifier = "==5.2.1" }, + { name = "django", specifier = "==5.2" }, { name = "django-allauth", specifier = ">=65.5.0" }, { name = "django-fernet-encrypted-fields", specifier = ">=0.3.0" }, { name = "django-scopes", specifier = ">=2.0.0" },