Skip to content

Instantly share code, notes, and snippets.

@melvyn-sopacua
Created June 23, 2017 10:42
Show Gist options
  • Save melvyn-sopacua/cf3047e76c5195004aed8bda91cb70c8 to your computer and use it in GitHub Desktop.
Save melvyn-sopacua/cf3047e76c5195004aed8bda91cb70c8 to your computer and use it in GitHub Desktop.
Inline form for a foreign key
from django.contrib.gis.db import models
from django.db.models.fields.related import (ForeignObject,
ForwardManyToOneDescriptor)
from .models import Address
def get_address(value: (Address, int, dict, None)) -> (Address, int, None):
if value is None:
return None
if isinstance(value, (int, Address)):
return value
defaults = value.copy()
if 'id' in value:
del value['id']
return Address.objects.get_or_create(defaults=defaults, **value)
class AddressDescriptor(ForwardManyToOneDescriptor):
def __set__(self, instance, value):
super().__set__(instance, get_address(value))
class AddressField(models.ForeignKey):
def __init__(self, **kwargs):
self.to = 'locality.Address'
self.on_delete = models.CASCADE
if 'to' in kwargs:
del kwargs['to']
if 'on_delete' in kwargs:
del kwargs['on_delete']
super().__init__(self.to, on_delete=self.on_delete, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
kwargs['to'] = self.to
kwargs['on_delete'] = self.on_delete
return name, path, args, kwargs
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
super(ForeignObject, self).contribute_to_class(
cls, name, private_only, **kwargs
)
setattr(cls, self.name, AddressDescriptor(self))
def formfield(self, form_class=None, choices_form_class=None, **kwargs):
from locality.forms import InlineAddressForm
return super(models.ForeignKey, self).formfield(
form_class=InlineAddressForm
)
from django import forms
from django.core.exceptions import ValidationError
from django.utils.html import format_html, mark_safe
from django.utils.translation import get_language
from django.utils.translation import ugettext
from .models import *
class AddressForm(forms.ModelForm):
def __init__(self, pk=None, **kwargs):
if pk is not None:
instance = self._meta.model.objects.get(pk=pk)
kwargs.update(instance=instance)
super().__init__(**kwargs)
class Meta:
fields = ('line1', 'line2', 'city', 'region', 'postal_code')
model = Address
def inline_form_factory(form_class, form_prefix: str) -> forms.Widget:
class InlineFormWidget(forms.Widget):
template_name = 'forms/widgets/inline_form.html'
_form_class = form_class
prefix = form_prefix
choices = ()
def get_prefix(self) -> str:
if self.prefix is None:
raise NotImplementedError(
MUST_IMPLEMENT.format(key='prefix')
)
return self.prefix
def get_context(self, name, value, attrs) -> dict:
context = super().get_context(name, value, attrs)
prefix = self.get_prefix()
form_kwargs = {'prefix': self.get_prefix()}
if value:
form_kwargs['pk'] = value
context['{}_form'.format(prefix)] = self._form_class(
**form_kwargs
)
return context
def value_from_datadict(self, data, files, name):
prefix = self.get_prefix()
return self._form_class(data=data, files=files, prefix=prefix)
def value_omitted_from_data(self, data, files, name):
return False
class Media:
css = {
'all': ('locality/css/fieldset.css',)
}
return InlineFormWidget
AddressWidget = inline_form_factory(AddressForm, 'address')
class InlineFormBase(forms.Field):
invalid_message = FIELD_INVALID
def __init__(self, limit_choices_to=None, **kwargs):
kwargs['required'] = True
kwargs['validators'] = []
super().__init__(**kwargs)
def widget_attrs(self, widget):
return {
'class': 'fieldset',
}
def has_changed(self, initial, data):
return True
def clean(self, value: AddressForm):
if value.is_valid():
instance = value.save()
if not instance:
raise ValidationError('form save returns None')
return instance
raise ValidationError(
self.invalid_message.format(field_name=self.label)
)
class InlineAddressForm(InlineFormBase):
widget = AddressWidget
from django.db import models
class Address(models.Model):
line1 = models.CharField(
max_length=100, verbose_name=_('Address line 1'),
help_text=_('Street address, P.O. box, company name, c/o')
)
line2 = models.CharField(
max_length=100, verbose_name=_('Address line 2'),
help_text=_('Apartment, suit, unit, building, floor, etc'),
blank=True, null=True,
)
city = models.CharField(
max_length=200, verbose_name=_('City')
)
region = models.CharField(
max_length=200, verbose_name=_('State/Province/Region'),
)
postal_code = models.CharField(
max_length=20, verbose_name=_('ZIP/Postal code')
)
def __str__(self):
return '{}, {}'.format(self.line1, self.city)
class Meta:
verbose_name = _('Address')
verbose_name_plural = _('Addresses')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment