Created
June 23, 2017 10:42
-
-
Save melvyn-sopacua/cf3047e76c5195004aed8bda91cb70c8 to your computer and use it in GitHub Desktop.
Inline form for a foreign key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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