I'm trying to populate a field called 'identification' using the primary key 'id'. However, as you know, there is no way to know what 'id' an objects is going to be before it has been saved. Therefore, stubbornly I did this:
def save(self, *args, **kwargs):
super(Notifications, self).save(*args, **kwargs)
self.identification = str(self.id)
Amusingly, this works in console:
>>>new = Notifications.objects.create( # some fields to be filled )
>>>new.identification
'3' (# or whatever number)
but when I go to my template to retrieve this object:
{% for each in notifications %}
Identification: {{ each.identification }}
{% endfor %}
reality strikes:
Identification:
What is happening? Why is it working in console but not in a template?. What approach do you suggest to use an auto-populated field in other fields?.
Thanks a lot!
The problem is that you're not saving the changes to your database.
It works in the terminal because that particular model instance (the python object - very much temporary) has the property identification filled. When you access it in a view or template, the save() method has not been called so the property / field is blank.
To make it work, call save again after your first save. Also, it might make sense to set the id only on model creation. One extra call per initial save isn't so big of a deal in most cases.
def save(self, *args, **kwargs):
add = not self.pk
super(MyModel, self).save(*args, **kwargs)
if add:
self.identification = str(self.id)
kwargs['force_insert'] = False # create() uses this, which causes error.
super(MyModel, self).save(*args, **kwargs)
Related
I want to set a minimum value for a DecimalField attribute on a form at the time when I instantiate it in the view. And I want to get that minimum value from an object I gather from the database. I made it (sort of) work by manually putting in the html form in the template, but want to refactor it to use the form class because I can do more useful things with data in the view than I can in the template.
Based on my reading of other questions and docs, I can't set attributes with the .initial argument. I thought likely I need to override the __init__ method on the form, but I'm pretty sure I'm not doing this right and it makes no sense syntactically. Here's what I have tried:
class BidForm(forms.Form):
bid = forms.DecimalField(decimal_places=2)
listing_bid = forms.CharField(widget=forms.HiddenInput())
def __init__(self, min_bid, listing_pk, *args, **kwargs):
super(BidForm, self).__init__(*args, **kwargs)
self.fields['bid'].min = min_bid
self.fields['listing_bid'] = listing_pk
My idea is to have this form take a min_bid and a listing_pk, and fill the "min" attribute on the html input with whatever is in the min_bid variable. I want to put the listing_pk that's passed in as the value in a hidden field called "listing_bid". If it helps clarify, I'm trying to generate html equivalent to:
<input type="number" name="bid" min="{{ listing.current_bid }}">
<input type="hidden" name="listing_bid" value="{{ listing.pk }}">
In the view, I'd like to say something like:
form = BidForm(min_bid=foo, listing_bid=bar)
Then pass that into the template context for rendering.
Does this make sense? I've found some discussion of it in the context of ModelForms but can't wrap my head around how to do this with regular forms.
Edit: for future reference, here is what worked for me. I deleted the bid attribute on the form because there's no case where I would want to init it without supplying a min_bid:
class BidForm(forms.Form):
listing_bid = forms.CharField(widget=forms.HiddenInput())
def __init__(self, min_bid, listing_pk, *args, **kwargs):
super(BidForm, self).__init__(*args, **kwargs)
self.fields['bid'] = forms.DecimalField(decimal_places=2, min_value=min_bid)
self.fields['listing_bid'].initial = listing_pk
You can make use of .initial attribute:
class BidForm(forms.Form):
bid = forms.DecimalField(decimal_places=2)
listing_bid = forms.CharField(widget=forms.HiddenInput())
def __init__(self, min_bid, listing_pk, *args, **kwargs):
super(BidForm, self).__init__(*args, **kwargs)
self.fields['bid'].min_value = min_bid
self.fields['listing_bid'].intial = listing_pk
I guess what I need to do it overwrite my model's save method but do correct me if I'm wrong/have better suggestion.
This is what my model looks like:
class MergingModel(models.Model):
some_field = models.TextField()
And this is the unit test that I want to pass:
class MergingModelTests(TestCase):
def test_duplicates_are_overwriten(self):
MergingModel.create(id=1)
MergingModel.create(id=1, some_field="abc")
self.assertEquals(MergingModel.objects.count(),1)
self.assetEquals(MergingModel.objects.get(id=1).some_field,"abc")
I tried overwriting save method to check if record with id=x exists but that raised recursion errors, my code for save method looked like:
def save(self, *args, **kwargs):
if MergingModel.objects.filter(id__exact=self.id):
original = MergingModel.objects.get(id=self.id)
original.some_field = self.some_field
original.save()
else:
super().save( *args, **kwargs)
Then I tried overwriting create but I get error:
Key (id)=(ID1) already exists
So I'm not really sure what to do anymore.
I have a model with a customized save() method that creates intermediate models if the conditions match:
class Person(models.Model):
integervalue = models.PositiveIntegerField(...)
some_field = models.CharField(...)
related_objects = models.ManyToManyField('OtherModel', through='IntermediaryModel')
...
def save(self, *args, **kwargs):
if self.pk is None: # if a new object is being created - then
super(Person, self).save(*args, **kwargs) # save instance first to obtain PK for later
if self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all(): # creates instances of intermediate model objects for all OtherModels
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
super(Person, self).save(*args, **kwargs) #should be called upon exiting the cycle
However, if editing an existing Person both through shell and through admin interface - if I alter integervalue of some existing Person - the changes are not saved. As if for some reason last super(...).save() is not called.
However, if I were to add else block to the outer if, like:
if self.pk is None:
...
else:
super(Person, self).save(*args, **kwargs)
the save() would work as expected for existing objects - changed integervalue is saved in database.
Am I missing something, or this the correct behavior? Is "self.pk is None" indeed a valid indicator that object is just being created in Django?
P.S. I am currently rewriting this into signals, though this behavior still puzzles me.
If your pk is None, super's save() is called twice, which I think is not you expect. Try these changes:
class Person(models.Model):
def save(self, *args, **kwargs):
is_created = True if not self.pk else False
super(Person, self).save(*args, **kwargs)
if is_created and self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all():
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
It's not such a good idea to override save() method. Django is doing a lot of stuff behind the scene to make sure that model objects are saved as they expected. If you do it in incorrectly it would yield bizarre behavior and hard to debug.
Please check django signals, it's convenient way to access your model object information and status. They provide useful parameters like instance, created and updated_fields to fit specially your need to check the object.
Thanks everyone for your answers - after careful examination I may safely conclude that I tripped over my own two feet.
After careful examination and even a trip with pdb, I found that the original code had mixed indentation - \t instead of \s{4} before the last super().save().
so i have a model form - this is for django's incredibly unintuitive editing-of-a-model process, and if anyone has a solid "for an idiot" tutorial, i'd be keen to hear about it!
The problem at hand is adding/setting a value to a modelForm field so that it shows up in the html.
SO, i have this code in my view logic:
class EditSaveModel(View):
def get(self,request,id=None):
form = self.getForm(request,id)
return self.renderTheForm(form,request)
def getForm(self,request,id):
if id:
return self.idHelper(request,id)
return PostForm()
which is called on a "get". So, here, I am either wanting to show a pre-completed form, or a new form!
Drilling into the idHelper:
def idHelper(self,request,id):
thePost = get_object_or_404(Post, pk=id)
if thePost.story.user != request.user:
return HttpResponseForbidden(render_to_response('errors/403.html'))
postForm = PostForm(instance=thePost)
postForm.fields.storyId.value = thePost.story.id **ANY NUMBER OF COMBOS HAVE BEEN TRIED!
return postForm
where i am getting a post object, checking it belongs to the active user, and then attaching a new value to it - the "storyId"
i also tried, above:
postForm.storyId.value = thePost.story.id
but that tells me that postForm has no storyId value to set!
and:
postForm.storyId = thePost.story.id
but that doesn't actually set the storyId - that is, in the html, no value is present.
having a look at my PostForm definition:
class PostForm(forms.ModelForm):
storyId = forms.IntegerField(required=True, widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(PostForm, self).__init__(*args, **kwargs)
class Meta:
model = Post
ordering = ['create_date']
fields = ('post',)
#some validation here!
#associates the new post with the story, and checks that the user adding the post also owns that story
def clean(self):
cleaned_data = super(PostForm, self).clean()
storyId = self.cleaned_data.get('storyId')
storyArray = Story.objects.filter(id=storyId,user=self.request.user.id)
if not len(storyArray): #eh, this means if self.story is empty.
raise forms.ValidationError('Whoops, something went wrong with the story you\'re using . Please try again')
self.story = storyArray[0]
return cleaned_data
right, so is this clear? To summerise:
I want a hidden storyId field attached to my PostForm, so that i always know which story a given post is attached to! Now, i know there might be other ways to do this - i might be able to add the foreignkey as "hidden" somehow? that is welcome, please tell me how! But i really want to have the foreignKey as a hidden field right now, so feel free to propose a different way, but also answer the foreignkey as hidden modelForm question too!
With all the code above, i would imagine that I can just have this in the html (because my form is definitely called "form"):
{% for hidden in form.hidden_fields %}
{{ hidden.errors }}
{{ hidden }}
{% endfor %}
or even
{{ form.storyId }}
but this doesn't work! storyId will never show up as a set value.
what is going on here?
Have you tried passing it in to the constructor?
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
self.story_id = kwargs.pop('story_id', None)
super(PostForm, self).__init__(*args, **kwargs)
self.fields['storyId'].initial = self.story_id
I would like to display a warning message if I am into an editing form and hide it if I am in a creation form of a Django ModelForm.
form.is_bound tell me if the form was previously populated but how to test if the ModelForm was set with an existing instance ?
I tried this hasattr(form.instance, 'pk') but is it the right way to do so ?
Cheers,
Natim
Try checking if form.instance.pk is None.
hasattr(form.instance, 'pk') will always return True, because every model instance has a pk field, even when it has not yet been saved to the database.
As pointed out by #Paullo in the comments, this will not work if you manually define your primary key and specify a default, e.g. default=uuid.uuid4.
Since the existed instance would be passed as an argument with the keyword instance to create the model-form, you can observe this in your custom initializer.
class Foo(ModelForm):
_newly_created: bool
def __init__(self, *args, **kwargs):
self._newly_created = kwargs.get('instance') is None
super().__init__(*args, **kwargs)
I encountered this issue but in my case, am using UUID for PK. Though the accepted answer is correct for most cases but fails if you are not using Django default auto increment PK.
Defining a model property gives me the ability to access this value from both, Model, View and Template as attribute of the model
#property
def from_database(self):
return not self._state.adding
I found that self.instance is set in super().init anyway
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None,
renderer=None):
...
if instance is None:
# if we didn't get an instance, instantiate a new one
self.instance = opts.model()
https://github.com/django/django/blob/65e03a424e82e157b4513cdebb500891f5c78363/django/forms/models.py#L300
so we can track instance just before super().init called.
So my solution is to override init method and set custom field to track in all followed form's methods.
def __init__(self, *args: Any, instance=None, **kwargs: Any) -> None:
super().__init__(*args, instance=instance, **kwargs)
self.is_new_instance = not bool(instance)
and usage:
def _any_form_method(self):
if self.is_new_instance: