models.py
class PhoneNumber(models.Model):
person = models.ManyToManyField(Person, related_name='phonenumbers', blank=True)
employee = models.ManyToManyField(Employee, related_name='phonenumbers', blank=True)
phone = models.CharField(max_length = 20)
For check exist phone I tried create save() method for PhoneNumber model:
def save(self, *args, **kwargs):
#check if phone exists
exist_phone = PhoneNumber.objects.filter(phone=self.phone).last()
if exist_phone:
new_people = self.person
if new_people:
for person in new_people:
exist_phone.person.add(person)
new_employees = self.employee
if new_employees:
for employee in new_employees:
exist_phone.employee.add(employee)
else:
super(PhoneNumber, self).save(*args, **kwargs)
It doesn't work because an error occurs:
"<PhoneNumber: >" needs to have a value for field "phonenumber" before this many-to-many relationship can be used.
But I want get existing object and add new value for m2m. How do i get values from posted m2m fields? Is there are possible to realize it in model?
Update
It's not possible to realize this in model.
I use drf and realized get_or_create in serializer
class PhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = PhoneNumber
fields = ('id', 'person', 'employee', 'phone')
def create(self, validated_data):
people = validated_data.pop('person')
employees = validated_data.pop('employee')
phone = validated_data['phone']
exist_phone = PhoneNumber.objects.filter(phone=phone).last()
if exist_phone:
phonenumber = exist_phone
for person in people:
person.phonenumbers.add(phonenumber)
for employee in employees:
employee.phonenumbers.add(phonenumber)
else:
phonenumber = PhoneNumber.objects.create(**validated_data)
return phonenumber
This is not very convenient, because you need to do the same for both drf and admin and for other forms.
You should use Signal in this case because M2M field can save after creating instance.
I think that you use post_save.
I recommend you read this doc.
https://docs.djangoproject.com/en/1.10/ref/signals/#django.db.models.signals.post_save
Related
I have a User model,
class User(AbstractBaseUser):
id = models.IntegerField(primary_key=True)
email = models.EmailField(unique=True)
I have another model named Company. The Company model has a reference to User model via an Integer field.
class Company(models.Model):
user_id = models.IntegerField(db_index=True)
name = models.CharField(max_length=100)
size = models.IntegerField(default=1)
I wanted to extract the company information along with user information.
basically I want a user object dictionary like this {'id':1, 'email':'abc#gmail.com','name':'foobar.co','size':400}
I want to annotate the user objects with name and size. As of now, I tried in the serializer's to_representation method. However, for millions of users this is super slow.
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
def to_representation(self, instance):
response = super(UserSerializer, self).to_representation(instance)
company = Company.objects.filter(user_id=instance.id)
if company.exists():
company = company.first()
response['name'] = company.name
response['size'] = company.size
return response
How can I achieve this annotation in the query itself.
If the links in the comment do not help you, You can use SerializerMethodField for name, and size
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
name = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
def get_name(self, obj):
# get name from DB using the User object(obj)
return name
def get_size(self, obj):
# get size from DB using the User object(obj)
return size
I am trying to update a model field by overriding save method:
class DataTable(models.Model):
id = models.AutoField(db_column='Id', primary_key=True)
country= models.ForeignKey(Countries,on_delete=models.DO_NOTHING,db_column='CountryId')
university=models.ManyToManyField(Universities,db_column='UniversityId',verbose_name='University',related_name='universities')
intake = models.CharField(db_column='Intake',blank=True, null=True, max_length=20, verbose_name='Intake')
year = models.CharField(max_length=5,blank=True,null=True)
application_status = models.ForeignKey(Applicationstages,on_delete=models.DO_NOTHING, db_column='ApplicationStageId',verbose_name='Application Status')
requested_count = models.PositiveIntegerField(db_column='RequestedCount',blank=True,null=True)
class Meta:
db_table = 'DataTable'
def __str__(self):
return str(self.application_status)
def __unicode__(self):
return str(self.application_status)
def save(self,*args, **kwargs):
super(DataTable,self).save(*args, **kwargs)
country_id= self.country
intake= self.intake
year= self.year
universities= self.university.all()
courses = get_ack_courses(country_id,universities)
all_data = get_all_courses(intake,year,courses)
ack_data = get_acknowledgements_data(all_data)
self.requested_count =len(ack_data)
super(DataTable,self).save(*args, **kwargs)
I am trying to update the requested_count field using the other field values, but in save method when I try to get the m2m field data ; it is returning empty. I tried with post_save signal also and there also m2m field data is empty.
I want the count field based on otherfields from this model. How to save when we have m2m fields?
I am trying to create a Model and ModelForm with "name" and "client" fields that have the following cleaning and validation characteristics. I can manage each individual requirement but can't seem get them to work together.
An authenticated user can enter a name for an Item
Item is saved with the name and forced to the client that is associated with the user account.
Name is cleaned via ' '.join(name.strip().split())
Name is validated so that (cleaned_name.lower(),client) is unique
EG: If "FOO BAR" exists in the user's associated client, user would get an error if they enter "foo bar"
It is a fairly simple model:
class Item(BaseModel):
class Meta:
unique_together = (("client", "name"),)
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
name = models.CharField(max_length=64, null=False, blank=False)
def clean_name(self):
return ' '.join(self.cleaned_data['name'].strip().split())
All item creates/updates are done via Django REST Framework:
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name')
def create(self,validated_data):
item = Item.objects.create(name=validated_data['name'],client=self.context['request'].user.client)
item.save()
return item
I would prefer as much of the logic in the Model as possible (eg, not use SQL to create indexes), but could push some of the validation to the serializer if need be.
Tx.
I ended up with the following. The only caveat is that I have to include a name_slug field to store for sorting purposes.
models.py
class Item(BaseModel):
class Meta:
db_table = 'item'
ordering = ['name_slug']
# relations
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
# attributes
name = models.CharField(max_length=64, null=False, blank=False)
name_slug = models.CharField(max_length=64, null=False, blank=True)
def clean(self):
self.name = ' '.join(self.name.strip().split())
if Item.objects.filter(client=self.client,name__iexact=self.name).count() > 0:
raise ValidationError({'name': 'Name already exists. Please enter a different name'})
def save(self, *args, **kwargs):
self.name_slug = '-'.join(self.name.split()).lower()
super(Item, self).save(*args, **kwargs)
serializers.py
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name','name_slug')
read_only_fields = ('name_slug',)
def validate(self, attrs):
attrs['client'] = self.context['request'].user.client
instance = Item(**attrs)
instance.clean()
return { k: getattr(instance,k) for k in attrs.keys()}
I have a model with some fields and a User as a ForeignKey
class Customer(models.Model):
#fields
salesman = models.ForeignKey(User, blank=True, null=True)
and a model form
class CustomerForm(ModelForm):
class Meta:
model = Customer
I want my form to validate if salesman is also entered but not on Database level. If I add the salesman field
class CustomerForm(ModelForm):
salesman = forms.ModelChoiceField(required=True, queryset=User.objects.all(), widget=Select(attrs{"class":"form-control"})
class Meta:
model = Customer
will that overide the models salesman field? Must I overide save method to save the newly created field's value to the models default one? Or does django form sees the same name so of field and uses it correctly?
You can redefine if clean method of the form:
class CustomerForm(ModelForm):
class Meta:
model = Customer
def clean(self, *args, **kwargs):
cleaned_data = super(CustomerForm, self).clean(*args, **kwargs)
salesman = cleaned_data.get('salesman')
if salesman:
# do something
else:
# you can rise form error if need
msg = u"Salesman is required"
self._errors["salesman"] = self.error_class([msg])
I generate field automaticly, so I want to hide it from user. I've tried editable = False and hide it from exclude = ('field',). All this things hide this field from me, but made it empty so I've got error: null value in column "date" violates not-null constraint.
models.py:
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(editable=False)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)`
admin.py:
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['date'] = datetime.now()
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
list_display = ('title','user',)
list_filter = ('date',)
Based on your model setup, I think the easiest thing to do would change your date field to:
date = models.DateTimeField(auto_now=True)
that should accomplish what you're after and you don't even need to exclude it from the admin, it's excluded by default. If you have auto_now=True it will act as a 'last update time'. If you have auto_now_add=True it will act as a creation time stamp.
There are several other ways you could accomplish your goal if your use case is more complex than a simple auto date field.
Override the model's save method to put the value in.
class Message(models.Model):
title=models.CharField(max_length=100)
date = models.DateTimeField(editable=False)
def save(*args, **kwargs):
self.date = datetime.datetime.now()
super(Message, self).save(*args, **kwargs)
What you are trying to do with the Model Admin isn't quite working because by default django only transfers the form fields back to a model instance if the fields are included. I think this might be so the model form doesn't try to assign arbitrary attributes to the model. The correct way to accomplish this would be to set the value on the instance in your form's save method.
class MessageAdminForm(forms.ModelForm):
def save(*args, **kwargs):
self.instance.date = datetime.now()
return super(MessageAdminForm, self).save(*args, **kwargs)