How to validate and create related objects together with forms - django

I am trying to validate a related object (ForeignKey) when creating an object the base object with forms. The related object may or may not exist. Below I use MPTT but this is a general foreign key problem.
I have a model like this:
# model:
class MyMPTTModel(models.Model):
name = models.CharField(max_length=256, unique=True) # this is set
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
#form
class MyMPTTModelForm(forms.ModelForm):
parent = mptt_forms.TreeNodeChoiceField(queryset=MyMPTTModel.objects.all())
class Meta:
model = MyMPTTModel
fields = ['name', 'parent']
I want to atomically get_or_create a set of nodes with the form(set?).
Something like:
paths = ['each-part/of-the-path/is-the-name', 'each-part/of-the-path/could-have-mutliple-children']
for path in paths:
parent = None
nodes = []
for p in path.split('/'):
nodes.append({'name': p, 'parent': parent })
parent = p
for node in nodes:
name, parent = node.values()
if parent:
parent = MyMPTTModel.objects.get_or_create(name=parent)[0]
MyMPTTModel.objects.get_or_create(name=name)
I'm struggling with the get_or_create part of the form as the parent may not exist and therefore is not a valid choice. I could create the parents before I create the next node, but then when it fails, it would create a bunch of orphan nodes since the children failed.
I want to validate each node and create them all together (or not).

Related

How can I delete all child objects of a parent object without deleting parent object with Django

I have two models that use the same class as foreign key.
I don't want to delete the parent model (which is being used as foreign key) but delete the child models that are associated with the parent model.
class A(models.Model):
pass
class B(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE, default=None)
class C(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE, default=None)
Now I am deleting them separately as below :
b_list = B.objects.filter(a = A)
for b_list_item in b_list:
b_list_item.delete()
c_list = C.objects.filter(a = A)
for c_list_item in c_list:
c_list_item.delete()
How can I delete them all together with a single command ?
I would prefer this bulk delete option instead of iterating over the objects.
B.objects.filter(a=A).delete()
C.objects.filter(a=A).delete()

Mirror/inherit a module for outputting some fields of the original

Is it possible, in Django, to create a module which is linked via a OneToOneField to another one, which only outputs a single field of its parent?
Something like:
class Venue(models.Model): # this is the parent
venue_name = models.CharField(max_length=50)
venue_city = models.CharField(max_length=50)
venue_country = models.CharField(max_length=50)
class VenueCity(models.Model): # should this be (Venue)?
venue_city = # this is the OneToOneField linked to the venue_city field of the parent
I need this because it'd be very handy for using it with a select2 field (
django_select2 - limit the returned text to a specific field in module) and I cannot use a #property, only a proper module.
** Addition: widget code **
class VenueForm(forms.ModelForm):
class Meta:
model = Venue
fields = ['venue_name', 'venue_city', 'venue_country']
widgets = {
'venue_city': s2forms.ModelSelect2Widget(model=Venue,
search_fields=['venue_city__icontains'])}
No, not as such. You could probably use Django-select2's label override function to show only the city name from the venue model, and maybe override the queryset too if you want uniquely cities only.

Django form to save m2m additional fields (using custom through/intermediary model)

I have a "Parent" model, which contains multiple "Child" models and the relationship between Parent and Child holds the order of the children. Thus, I need a custom intermediary model.
models.py:
class Parent(models.Model):
name = models.CharField
children = models.ManyToManyField(Child, through=ParentChild)
...
class Child(models.Model):
name = models.CharField()
...
class ParentChild(model.Model):
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
child = models.ForeignKey(Child, on_delete=models.CASCADE)
order_of_child = IntegerField(null=True, blank=True, unique=True, default=None)
A Parent object is created based on a form and the Child objects to be part of the Parent object are selected by checkboxes.
Now my questions are: How can the order_of_child be included in the form and rendered alongside the checkboxes and how can the relationship be correctly saved in the view?
forms.py:
class ParentForm(ModelForm):
class Meta:
model = Parent
fields = ['name', 'children']
def __init__(self, *args, **kwargs):
super(ParentForm, self).__init__(*args, **kwargs)
self.fields['name'] = forms.CharField(label='Name', widget=forms.TextInput()
self.fields['children'] = ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(queryset=Child.objects.all())
To save the relationship, you just first create and save the Parent, then loop through form.cleaned_data['children'] to create each ParentChild instance, assigning the index of the loop as the order.
Now for the order, that's more tricky, you'll need a widget that allows you to reorder the selection, so the default CheckboxSelectMultiple won't work because it doesn't do that. You need some javascript for that: Copy the selected options to a new div where the user can drag & drop to change the order, e.g. using a library such as jquery.ui.sortable or sortableJS/sortable. With javascript, you populate a hidden field with the selected values in the right order, which is what you submit in the end.
There's also a special django sortable multiselectfield package which I haven't tried (uses jqueryUI for sorting).

Getting a field value from a field via a ForeignKey widget in reverse with django-import-export

I have it almost working.
Models:
class Child(models.Model):
parent = models.ForeignKey('project.Parent')
name = models.CharField(max_length=100)
class Parent(models.Model):
text = models.CharField(max_length=100)
Resource:
class ParentResource(resources.ModelResource):
children = fields.Field(widget=widgets.ForeignKeyWidget(Parent))
class Meta:
model = Parent
use_transactions = True
fields = ('text', 'children__child__name')
Then the view calls the resource and downloads it. The issue is, name is blank. So, everything else works just fine, but I can't get child.name to show up. What am I missing?
First of all, widgets.ForeignKeyWidget is used by modelA to look up a related modelB that is a ForeignKey of modelA.
I.e. a ChildResource can use widgets.ForeignKeyWidget to look up a Parent, but not vice versa.
Doing it in reverse, i.e. to loop up and/or display some fields of a set of Childs (who has a ForeignKey Parent) from a ParentResource, you need to do something like this:
from import_export import fields, resources
from .models import Parent
class ParentResource(resources.ModelResource):
children = fields.Field()
class Meta:
model = Parent
use_transactions = True
fields = ('text', 'children')
def dehydrate_children(self, parent):
children = list(parent.child_set.all())
return ','.join([child.name for child in children])
Using the dehydrate() method. Then when you export ParentResource, you'll get a dataset object with a "children" key whose value is a comma-separated list of the names of its children.

Could ForeignKey field link different type Models in Django?

I need to use Django to achieve something like a tree, node in this tree has a single parent, and many children.The problem is that the type of the nodes are not the same, they belong to different Model, although they all have one foreign keys named parent, but different in other fields and methods, and each node's parent is likely to be any other types of nodes.for a node, I want to know who is its parent and who is its childs.
I tried this:
class BaseNode(models.Model):
parent = models.ForeignKey("self", blank=True, null=True, related_name="child_set")
def add_child(self, node):
self.child_set.add(node)
self.save()
def get_children(self, node):
return self.child_set.all()
class Meta:
abstract = True
class ANode(BaseNode):
pass
class BNode(BaseNode):
pass
When I execute this:
a = ANode()
b = BNode()
a.add_child(b)
There is an error:
TypeError: 'ANode' instance expected, got <BNode: BNode object>