I got the two following models:
class Stuff(models.Model):
...
def custom_function(self):
...
class MyModel(models.Model):
name = models.CharField(max_length=200)
many_stuff = models.ManyToManyField(Stuff, related_name="many_stuff+")
many_other_stuff = models.ManyToManyField(Stuff, related_name="many_other_stuff+")
def __init__(self, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
for stuff in self.many_stuff.all():
many_stuff.custom_function()
def on_save(self, *args, **kwargs):
for stuff in self.many_stuff.all():
many_stuff.custom_function()
super(MyModel, self).save(*args, **kwargs)
When I create a new instance of MyModel through the Django Admin, I want to execute for each Stuff object, a function. It works on_save but I cannot make it work on __init__ for some reason. If I create the new instance, the functions don't get executed. If I save the newly created instance, the functions do get executed.
To be more specific, when I use this __init__ method, the model breaks on instance creation with the error:
MyModel needs to have a value for field "id" before this many-to-many relationship can be used.
I also attempted doing the same thing with the post_save signal, instead of using __init__ and on_save but I had the same problem. On object creation, the function custom_function did not get executed, but it did when I saved the object after it has been created by clicking on the save button.
Any ideas?
You simply cant create M2M join while creating object.
Because M2M model fields don't have real db fields, but have intermediary table that stores id from both model. When you create relation between two objects, both objects must have an id value ready to use.
First create MyModel instance without M2M fields, than set M2M fields. Something like that;
newModel = MyModel.objects.create(name=someting)
newStuff = Stuff.objects.create(..)
newModel.many_stuff.add(newStuff)
Related
I've got a Django model like so...
class Example(models.Model):
title = models.CharField(...)
...
I'm trying to compare two values - the title field before the user changes it, and the title after. I don't want to save both values in the database at one time (only need one title field), so I'd like to use pre_save and post_save methods to do this. Is it possible to get the title before the save, then hold this value to be passed into the post_save method?
The pre_save and post_save methods look like so...
#receiver(pre_save, sender=Example, uid='...')
def compare_title_changes(sender, instance, **kwargs):
# get the current title name here
x = instance.title
#receiver(post_save, sender=Example, uid='...')
def compare_title_changes(sender, instance, **kwargs):
# get the new title name here and compare the difference
x = instance.title # <- new title
if x == old_title_name: # <- this is currently undefined, but should be retrieved from the pre_save method somehow
...do some logic here...
Any ideas would be greatly appreciated!
Edit
As was pointed out to me, pre_save and post_save both occur after save() is called. What I was looking for is something like pre_save() but before the actual save method is called. I set this on the model so that the logic to be performed will be accessible wherever the instance is saved from (either admin or from a user view)
Use Example.objects.get(pk=instance.id) to get the old title from the database in the pre_save handler function:
#receiver(pre_save, sender=Example, uid='...')
def compare_title_changes(sender, instance, **kwargs):
new_title = instance.title # this is the updated value
old_title = Example.objects.get(pk=instance.id)
# Compare the old and new titles here ...
This trick was proposed here a long time ago. I've not tested it with the recent Django version. Please let me know whether it's still working.
We can only say object has changed if "save" method passes successfully, so post_save is good to be sure that model object has updated.
Setting on the fly attribute on model class instance can do the task, as the same instance is passed from pre_save to post_save.
def set_flag_on_pre_save(sender, instance, *args, **kwargs):
# Check here if flag setting condition satisfies
set_the_flag = true
if set_the_flag:
instance.my_flag=0
def check_flag_on_post_save(sender, instance, *args, **kwargs):
try:
print(instance.my_flag)
print('Flag set')
except AttributeError:
print('Flag not set')
pre_save.connect(set_flag_on_pre_save, sender=ModelClass)
post_save.connect(check_flag_on_post_save, sender=ModelClass)
I've got a simple CBV for all CRUD operations on my model (let's say Book). How should I implement getting object by id and getting a list of all objects? Seems there are many options like:
Just create two separate classes Book and BookList.
Write some kind of dispatcher inside get method.
class BookView(View):
def get(self, request, *args, **kwargs):
if 'id' in self.kwargs:
self.get_object(request, *args, **kwargs)
else:
self.get_list(request, *args, **kwargs)
Override View.dispatch method so that it will call get_list() method if no id provided.
etc...
So what is best way?
Best way for crud API's is to use DRF and subclass the generics.ListCreateAPIView to get a list of all objects or create new object.
generics.RetrieveUpdateDestroyAPIView to get an object by id or to destroy and update the object by id. Like this:
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete a book instance.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
You will probably have to create your serializers accordingly.
I have this code
Task.objects.bulk_create(ces)
Now this is my signal
#receiver(pre_save, sender=Task)
def save_hours(sender, instance, *args, **kwargs):
logger.debug('test')
Now this signal is not triggered in bulk create
I am using django 1.8
As mentioned bulk_create does not trigger these signals -
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#bulk-create
This method inserts the provided list of objects into the database in
an efficient manner (generally only 1 query, no matter how many
objects there are).
This has a number of caveats though:
The model’s save() method will not be called, and the pre_save and post_save signals will not be sent.
It does not work with child models in a multi-table inheritance scenario.
If the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does.
It does not work with many-to-many relationships.
The batch_size parameter controls how many objects are created in single query. The default is to create all objects in one batch,
except for SQLite where the default is such that at most 999 variables
per query are used.
So you have to trigger them manually. If you want this for all models you can override the bulk_create and send them yourself like this -
class CustomManager(models.Manager):
def bulk_create(items,....):
super().bulk_create(...)
for i in items:
[......] # code to send signal
Then use this manager -
class Task(models.Model):
objects = CustomManager()
....
Iterating on the answer above:
Python 2:
class CustomManager(models.Manager):
def bulk_create(self, objs, **kwargs):
#Your code here
return super(models.Manager,self).bulk_create(objs,**kwargs)
Python 3:
class CustomManager(models.Manager):
def bulk_create(self, objs, **kwargs):
#Your code here
return super(CustomManager, self).bulk_create(objs,**kwargs)
class Task(models.Model):
objects = CustomManager()
....
Complete answer in python 2:
class CustomManager(models.Manager):
def bulk_create(self, objs, **kwargs):
a = super(models.Manager,self).bulk_create(objs,**kwargs)
for i in objs:
post_save.send(i.__class__, instance=i, created=True)
return a
I have a class like so:
class EmailForm(forms.Form):
users = forms.MultipleChoiceField(required=False, widget=MultipleHiddenInput())
subject = forms.CharField(max_length=100)
message = forms.Textarea()
def __init__(self, users, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.users.choices = users
# self.fields['users'].choices = []
The commented line at the bottom works perfectly if I use it instead of self.users.
Am I right in thinking that users, subject and message are class level so that is why they are popped out of the attribute list?
So self.fields is the per object copy of the attributes in case I want to change them in some way?
Thanks.
The Form class uses the DeclarativeFieldsMetaclass, which enables the declarative syntax for the fields.
The implementation means that the form class and instance does not actually have an attribute self.field_name for each field. That is why trying to use self.users gives an error.
The fields of the form instance can be accessed as self.fields, which is created when you call super in the __init__ method.
The fields of the form class can be accessed as self.base_fields.
I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value