either or fields django models - django

Hi i am creating a django model for a type of subscription for books
class Subscription(models.Model):
user = models.ForeignKey(User,null=True, blank=True)
group = models.ForeignKey(Group, null=True, blank=True)
book_list = models.ForeignKey(Books, null=True)
def create(cls, (user, group), **kwds):
return cls(user=user, group=group, books=books, **kwds)
I want to create this in such a way that either the field user is chosen or the field Group but not both
the above syntax gives an error " User object not iterable"
can anyone help me with this?
Thanks in advance

First thing if you wish to create as such, one suggestion would be add a Manager to this model, for eg. SubscriptionManager and in that write create method. It could be something like this.
def create(self, subscriber, **kwds):
if isinstance(subscriber, User):
#save subscriber instance in user field
elif isinstance(subsriber, Group):
#save subscriber instance is group field
PS: I have only provided solution for either or question, take care of books_list as well

Related

How to create multiple model instance (have one-to-many relationship) with an API call in DRF

I have 2 models Product and Watch like below
class Product(models.Model):
id = models.CharField(max_length=255, primary_key=True)
# other fields
And the Watch model have the product_id foreign key connected with Product model
class Watch(models.Model):
product_id = models.ForeignKey(
Product,
related_name='watches',
on_delete=models.CASCADE,
null=True,
)
# other fields
When I call an API to create a new watch with product_id in the request body. I want to check if the product with that product_id does exist or not. If not, I create a new product instance at the same time.
Currently, I'd overiding the create() method in serializer class like so
def create(self, validated_data):
product_id = validated_data['product_id']
is_product_exist = self.is_product_exist(product_id)
if (is_product_exist):
watch = Watch.objects.create(**watch_data)
else:
Product.objects.create(**product_data)
watch = Watch.objects.create(**watch_data)
return watch
But when calling API, I got the error
{"product_id":["Invalid pk \"57668290\" - object does not exist."]}
So how can I deal with this error? thank you guys
you could use Django's get_or_create method which is a lot better.
it returns the object and a bool specifying if it was fetched or created
https://docs.djangoproject.com/en/3.1/ref/models/querysets/#get-or-create
def create(self, validated_data):
product_id = validated_data.pop('product_id',None)
if product_id is None:
# create an error to handle missing data here if you want to
raise serializers.ValidationError("You have to specify product id")
product, created_bool = Product.objects.get_or_create(id=product_id)
watch_data['product'] = product # not a mandotray row, assign the new product to the watch_data if you want to associate them
watch = Watch.objects.create(**watch_data)
return watch

How to update model field after saving instance?

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()

Only allow filling one field or the other in Django model and admin

I've got a simple Django model for a Group that has a list of Contacts. Each group must have either a Primary contact ForeignKey or a All contacts BooleanField selected but not both and not none.
class Group(models.Model):
contacts = models.ManyToManyField(Contact)
contact_primary = models.ForeignKey(Contact, related_name="contact_primary", null=True)
all_contacts = models.BooleanField(null=True)
How can I ensure that:
The model can't be saved unless either contact_primary or all_contacts (but not both) is set. I guess that would be by implementing the Group.save() method? Or should it be the Group.clean() method??
In Django Admin either disable the other field when one is selected or at least provide a meaningful error message if both or none are set and the admin tries to save it?
Thanks!
The easiest way would be to override the save() method of your model:
class Group(models.Model):
contacts = models.ManyToManyField(Contact)
contact_primary = models.ForeignKey(Contact, related_name="contact_primary", blank=True, null=True)
all_contacts = models.BooleanField(blank=True, null=True)
def save(self, *args, **kwargs):
if self.contact_primary is not None and self.all_contacts is not None:
raise Exception("some error message here")
if self.contact_primary is None and self.all_contacts is None:
raise Exception("some other error message here")
return super().save()
Notice that I added blank=True to your model fields. This is necessary if you want to insert null columns through the admin or through a form.
Update
If you want to raise a ValidationError, you must raise it from the model's clean() method. Otherwise, you will give a 500 error to the client, rather than an error message.

Django Models: Reference the same field twice in a fk model

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.

M2M using through and form with multiple checkboxes

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.