I'm writing a simple application with the models Person and Project, which have a many to many relationship through the Membership model. Only one Membership can exist between any two given Person and Project objects. Currently, new Membership objects can be added via inline from both the Person and Project change forms.
This works fine, but there's the problem of a user accidentally creating a redundant Membership. I've set a unique_together constraint for the two foreign keys in the Membership model, however this has two problems:
When saving a new parent object, any redundant instances of the Membership model are saved without raising any kind of error.
When modifying an existing object, adding a redundant Membership triggers the expected error message, but the delete icon of the corresponding inline is not shown and it's not possible to remove one of the objects that have already been persisted because the error prevents any database operations from happening.
I'd like to know what's the right way of iterating through the membership_set collection before the parent object is saved and removing any redundant objects.
this is how I made in my project:
#Models.py
class Person(models.Model):
user = models.OneToOneField(User, primary_key=True)
field = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
The view
#views.py
class GroupDetails(generic.DetailView):
model = Group
template_name = 'app/template.html'
form_class = SomeForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
#get group
group = self.get_object(queryset=Group.objects.all())
#get person
person = Person.objects.get(pk=request.user)
#check if objects exists before save
if Membership.objects.filter(person = person, group = group).exists():
messages.error(request, 'Oh this is duplicated')
return HttpResponseRedirect(reverse('view-name', kwargs={'pk': group.pk}))
else:
if form.is_valid():
form.save(commit=False)
#assign to the through table
persontogroup = Membership.objects.create(person = person, group = group)
persontogroup.save()
messages.success(request, 'Success!')
return HttpResponseRedirect('/something/')
Hope this helps. You can also check: Avoid Django def post duplicating on save
Related
I have a Django model Article and after saving an instance, I want to find the five most common words (seedwords) of that article and save the article in a different model Group, based on the seedwords.
The problem is that I want the Group to be dependent on the instances of Article, i.e. every time an Article is saved, I want Django to automatically check all existing groups and if there is no good fit, create a new Group.
class Article(models.Model):
seedword_group = models.ForeignKey("Group", null=True, blank=True)
def update_seedword_group(self):
objects = News.objects.all()
seedword_group = *some_other_function*
return seedword_group
def save(self, *args, **kwargs):
self.update_seedword_group()
super().save(*args, **kwargs)
class Group(models.Model):
*some_fields*
I have tried with signals but couldn't work it out and I'm starting to think I might have misunderstood something basic.
So, to paraphrase:
I want to save an instance of a model A.
Upon saving, I want to create or update an existing model B depending on A via ForeignKey.
Honestly I couldn't understand the rationale behind your need but I guess below code may help:
def update_seedword_group(content):
objects = News.objects.all()
"""
process word count algorithm and get related group
or create a new group
"""
if found:
seedword_group = "*some_other_function*"
else:
seedword_group = Group(name="newGroup")
seedword_group.save()
return seedword_group
class Group(models.Model):
*some_fields*
class Article(models.Model):
seedword_group = models.ForeignKey("Group", null=True, blank=True)
content = models.TextField()
def save(self):
self.group = update_seedword_group(self.content)
super().save()
I have the following model and modelform for an Employee:
models.py
class Employee(models.Model):
reports_to = models.ForeignKey(
'self', on_delete=models.SET_NULL,
null=True, blank=True)
forms.py
class EmployeeForm(forms.ModelForm):
class Meta:
model = Employee
The idea is that the boss of an Employee is themselves an Employee.
The problem is that, when I'm updating the instance, the respective form field created is a dropdown with all Employees, including the object I'm updating itself.
Is there an easy way to remove the instance itself from the dropdown options so that no employee has him/herself as their own boss?
PS.: I'm not looking for a solution that validates the form field after submitting a form, but rather removing the option from the form dropdown altogether. Thanks!
Yes, you can modify the queryset of the respective field, and omit the instance, if that instance (already) exists. Like:
class EmployeeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = self.instance
if instance.pk is not None:
self.fields['reports_to'].queryset = Employee.objects.exclude(pk=instance.pk)
class Meta:
model = Employee
In case the instance has a pk that is not None (that means that you edit the instance, not create a new one), then we thus "patch" the queryset that contains all the Employees, except for that one.
So here's the issue i'm having with this Django Model.
I have a foriegn key to class SecretSantaGroup in class assignees called group.
I want to reference this fk in creator and assignee.
pretty much the data I want is like this:
creator = self.group.members
assignee = self.group.members
But I'm having issues on going about it and could use some help.
I want to be able to reference all the users in that specific group, just having trouble going about it.
class SecretSantaGroups(models.Model):
groupName = models.TextField()
members = models.ManyToManyField(User)
def __str__(self):
return self.groupName
class Meta:
verbose_name_plural = 'Secret Santa Groups'
class assignees(models.Model):
group = models.ForeignKey(SecretSantaGroups)
#person that gives gifts
creator = models.ForeignKey(self.group.members, null=True)
#person who receives gift
assignee = models.ForeignKey(self.group.members, null=True)
EDIT
---I used terrible wording, the assignees class is supposed to be who gets who in the group. 1 person gets another in each secret santa group. so gifter and giftee
class assignees(models.Model):
group = models.ForeignKey(SecretSantaGroups)
#person that gives gifts
giver = models.???(self.group.members, null=True)
#person who receives gift
giftee = models.???(self.group.members, null=True)
Unless I'm mistaken, it seems like what you're trying to do is define an association with all possible users for the SecretSantaGroup and then define which of those users is "assigned" or whatever you want to call it.
I also don't know if you want to edit these within Django admin, or part of a different view, but how I would define the model is as such:
class SecretSantaGroup(models.Model):
name = models.CharField(max_length=255)
creator = models.ForeignKey(User, related_name='creator')
members = models.ManyToMany(User, related_name='members')
assigned = models.ManyToMany(User, related_name='assigned', blank=True, null=True)
def __unicode__(self):
return self.name
If you want to limit the choices of "assigned" in Django admin, you'll need to do this in two steps. First, you would need to assign which members, then you'd need to assign the QuerySet of "assigned" to the objects in members so the choices are limited, and then you can assign which ones you want.
This can be done via a custom form. I have NOT tested this code:
class SecretSantaGroupForm(forms.ModelForm):
class Meta:
model = SecretSantaGroup
def __init__(self, *args, **kwargs):
super(SecretSantaGroupForm, self).__init__(*args, **kwargs)
self.fields['assigned'].queryset = self.instance.members.all()
Then you can assign the custom admin form on your model admin.
If you're doing this on the public side, you'll still need the same type of override, and you'll still have to do this in two steps, as best as I can tell. Hope that helps you out.
I'd like to create a form allowing me to assign services to supplier from these models. There is no M2M relationship defined since I use a DB used by others program, so it seems not possible to change it. I might be wrong with that too.
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
contact = models.ForeignKey(Contact, null=False, blank=False)
class SupplierPrice(models.Model):
service_user = models.ForeignKey('ServiceUser')
price_type = models.IntegerField(choices=PRICE_TYPES)
price = models.DecimalField(max_digits=10, decimal_places=4)
I've created this form:
class SupplierServiceForm(ModelForm):
class Meta:
services = ModelMultipleChoiceField(queryset=Service.objects.all())
model = ServiceUser
widgets = {
'service': CheckboxSelectMultiple(),
'contact': HiddenInput(),
}
Here is the view I started to work on without any success:
class SupplierServiceUpdateView(FormActionMixin, TemplateView):
def get_context_data(self, **kwargs):
supplier = Contact.objects.get(pk=self.kwargs.get('pk'))
service_user = ServiceUser.objects.filter(contact=supplier)
form = SupplierServiceForm(instance=service_user)
return {'form': form}
I have the feeling that something is wrong in the way I'm trying to do it. I have a correct form displayed but it is not instantiated with the contact and checkboxes aren't checked even if a supplier has already some entries in service_user.
You are defining services inside your Meta class. Put it outside, right after the beginning of SupplierServiceForm. At the very least it should show up then.
Edit:
I misunderstood your objective. It seems you want to show a multiple select for a field that can only have 1 value. Your service field will not be able to store the multiple services.
So, by definition, your ServiceUser can have only one Service.
If you don't want to modify the database because of other apps using it, you can create another field with a many to many relationship to Service. That could cause conflicts with other parts of your apps using the old field, but without modifying the relationship i don't see another way.
The solution to my problem was indeed to redefine my models in oder to integrate the m2m relationship that was missing, using the through argument. Then I had to adapt a form with a special init method to have all selected services displayed in checkboxes, and a special save() method to save the form using m2m relationship.
class Supplier(Contact):
services = models.ManyToManyField('Service', through='SupplierPrice')
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
supplier = models.ForeignKey(Supplier, null=False, blank=False)
price = models.Decimal(max_digits=10, decimal_places=2, default=0)
And the form, adapted from the very famous post about toppings and pizza stuff.
class SupplierServiceForm(ModelForm):
class Meta:
model = Supplier
fields = ('services',)
widgets = {
'services': CheckboxSelectMultiple(),
'contact_ptr_id': HiddenInput(),
}
services = ModelMultipleChoiceField(queryset=Service.objects.all(), required=False)
def __init__(self, *args, **kwargs):
# Here kwargs should contain an instance of Supplier
if 'instance' in kwargs:
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data (checked boxes).
initial['services'] = [s.pk for s in kwargs['instance'].services.all()]
ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
supplier = ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
def save_m2m():
new_services = self.cleaned_data['services']
old_services = supplier.services.all()
for service in old_services:
if service not in new_services:
service.delete()
for service in new_services:
if service not in old_services:
SupplierPrice.objects.create(supplier=supplier, service=service)
self.save_m2m = save_m2m
# Do we need to save all changes now?
if commit:
self.save_m2m()
return supplier
This changed my first models and will make a mess in my old DB but at least it works.
I have two models that I need to create a form for.
def Item(models.Model):
title = models.CharField()
description = models.TextField()
def ItemUser(models.Model):
item = models.ForeignKey(Item)
user = models.ForeignKey(User)
I need to make a form where a user can create a new Item and be able to add/remove multiple ItemUser. Since I am using email address as a user id, I plan to have that entered as input for ItemUser. So the form should render input boxes for taking email addresses and then on the backend I would need to fetch user ids for these email addresses to save into ItemUser table.
How do I implement this form in Django?
EDIT:
Adding another model example to add clarity.
def Blog(models.Model):
creator = models.ForeignKey(User)
title = models.CharField()
description = models.TextField()
def BlogAccessUser(models.Model):
blog = models.ForeignKey(Blog)
access_user = models.ForeignKey(User)
Each blog has a list of users that are allowed to access it. So in the form a user will create a blog and also add multiple users to this blog. Something like "Share" feature in Google DOCS where I can add multiple users to my document.
where a user can create a new item
So this would suggest an Item belongs to one user, one-to-one relationship. So lets start off with this...
def Item(models.Model):
title = models.CharField()
description = models.TextField()
user = models.OneToOneField(User, related_name="item")
be able to add/remove multiple ItemUser
This would suggest a many-to-many relationship so you will need to add this also to item model...
def Item(models.Model):
title = models.CharField()
description = models.TextField()
user = models.OneToOneField(User, related_name="item")
item_users = models.ManyToManyField(ItemUser, related_name="item")
I need to make a form
So for this you create a model form, which you can filter on email, as you can see email is passed when you create the form init.
class ItemForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, email=None, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
# Representing the many to many related field in Item
ItemUsers = forms.ModelMultipleChoiceField(queryset=ItemUser.objects.filter(user__email=email))
self.fields['item_users'] = forms.ModelChoiceField(queryset=ItemUsers)
Thats it, in your view just pass the email for filtering, form = itemForm(email=email)
Above was freehand so there could be a few mistakes in their, but it should give you the right idea, hope this helps.
The answer to the original question was to use formsets in Django. I ended up using an InlineFormset and writing a custom render method for the form. I also used JS to dynamically add/remove forms.