Django: Overriding the save() method: how do I call the delete() method of a child class - django

The setup =
I have this class, Transcript:
class Transcript(models.Model):
body = models.TextField('Body')
doPagination = models.BooleanField('Paginate')
numPages = models.PositiveIntegerField('Number of Pages')
and this class, TranscriptPages(models.Model):
class TranscriptPages(models.Model):
transcript = models.ForeignKey(Transcript)
order = models.PositiveIntegerField('Order')
content = models.TextField('Page Content', null=True, blank=True)
The Admin behavior I’m trying to create is to let a user populate Transcript.body with the entire contents of a long document and, if they set Transcript.doPagination = True and save the Transcript admin, I will automatically split the body into n Transcript pages.
In the admin, TranscriptPages is a StackedInline of the Transcript Admin.
To do this I’m overridding Transcript’s save method:
def save(self):
if self.doPagination:
#do stuff
super(Transcript, self).save()
else:
super(Transcript, self).save()
The problem =
When Transcript.doPagination is True, I want to manually delete all of the TranscriptPages that reference this Transcript so I can then create them again from scratch.
So, I thought this would work:
#do stuff
TranscriptPages.objects.filter(transcript__id=self.id).delete()
super(Transcript, self).save()
but when I try I get this error:
Exception Type: ValidationError
Exception Value: [u'Select a valid
choice. That choice is not one of the
available choices.']
... and this is the last thing in the stack trace before the exception is raised:
.../django/forms/models.py in save_existing_objects
pk_value = form.fields[pk_name].clean(raw_pk_value)
Other attempts to fix:
t =
self.transcriptpages_set.all().delete()
(where self = Transcript from the
save() method)
looping over t (above) and deleting each item individually
making a post_save signal on TranscriptPages that calls the delete method
Any ideas? How does the Admin do it?
UPDATE: Every once in a while as I'm playing around with the code I can get a different error (below), but then it just goes away and I can't replicate it again... until the next random time.
Exception Type:
MultiValueDictKeyError Exception
Value: "Key 'transcriptpages_set-0-id'
not found in "
Exception Location:
.../django/utils/datastructures.py in
getitem, line 203
and the last lines from the trace:
.../django/forms/models.py in _construct_form
form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs)
.../django/utils/datastructures.py in getitem
pk = self.data[pk_key]

In the end it was a matter of timing. When deleting only a single child object out of many, there was no problem. If I was deleting too many child objects at once, the error could happen, because the delete action was attempting to reference ids that were not around. This is why nothing worked, not signals, not [object]_set. I fixed it by using jquery to set a hidden variable in the edit form for the child object, which caused the object to first process the update (slowing down it's processing) and THEN the delete.

You are probably trying to access the Transaction.id before is has been created. Additionally, you can try to access the TransactionPage objects through the Transaction object via transactionpage_set (see query docs about FOO_set notation).
def save(self):
super(Transcript, self).save()
if self.doPagination:
self.transaction_set.all().delete() # .all() may be optional
pages = ... # whatever you do to split the content
self.numPages = len(pages)
self.save() # yes, a second save if you store numPages in Transaction
for page_number in range(self.numPages):
TransactionPage.objects.create(content=pages[page_number],
order=page_number, transaction=self)
You could also switch to not storing numPages in the Transaction and access it via a property instead.
class Transaction(models.Model):
# ...
# replace numPages with
#property
def page_count(self):
return self.transactionpage_set.count() or 1
And then if you took it one step further you could always use the TransactionPage objects for display purposes. This would allow you to get rid of the extra self.save() call in the above save() method. It will also let you simplify your templates by always displaying TransactionPage.content instead of conditionally displaying Transaction.body if you are paginating and TransactionPage.content otherwise.
class Transaction(models.Model):
body = models.TextField()
paginate = models.BooleanField()
#property
def page_count(self):
return self.transactionpage_set.count() # no more "or 1" cruft!
def save(self):
super(Transcript, self).save()
self.transaction_set.delete() # might need an .all() before .delete()
if self.paginate:
# Do whatever you do to split the body into pages.
pages = ...
else:
# The only page is the entire body.
pages = [self.body]
for (page_number, page_content) in enumerate(pages):
TransactionPage.objects.create(content=page_content,
order=page_number, transaction=self)

Another possible solution might be to override save_formset() in the ModelAdmin to prevent the update completely:
def save_formset(self, request, form, formset, change):
if (form.cleaned_data['do_pagination'] and
formset.model == TranscriptPages):
formset.changed_objects = []
formset.new_objects = []
formset.deleted_objects = []
else:
formset.save()
Then, you can do whatever you like in Modal.save(). Note that there's probably a more elegant/breakproof way to stop the formset from processing if one wanted to dig into the internals a bit more.

Related

Django object "lock" and context rendering

I have a simple(I think) question, about Django context rendering.
I'll step right into it -
Basically what I need is, some temp table, which in my case, I called Locked. And when a user presses a button, which Is a form, that object goes straight to the table Locked(just a simple insert). And inside that table there is a field called is_locked, and if its True, that object needs to go gray, or to have some lock icon inside the html table.
Just some kind of a viewable sign, that an object is inside the table Locked, and that another user can't access it.
But, my problem is, since in my views.py, my lock function is not returning exact html where I want to render that locker icon, instead, it returns another html.
Is there any way, to render same context, on 2 html pages? Thank's.
This is my code :
views.py
def lock(request, pk):
# Linking by pk.
opp = get_object_or_404(OpportunityList, pk=pk)
opp_locked = get_object_or_404(Locked, pk=pk)
# Taking two parametters for 2 fields.
eluid = Elementiur.objects.get(eluid=pk)
user = User.objects.get(username=request.user)
# Dont bother with this one! Just pulling one field value.
field_name = 'optika_korisnik'
obj = OpportunityList.objects.get(pk=pk)
field_object = OpportunityList._meta.get_field(field_name)
field_value = getattr(obj, field_object.attname)
# This is the main part! This is where i'm inserting data into Locked table.
if opp_locked.DoesNotExist:
opp_locked.id = int(eluid.eluid)
opp_locked.locked_eluid = eluid
opp_locked.locked_comment = field_value
opp_locked.locked_user = user
opp_locked.locked_name = 'Zaključao korisnik - ' + request.user.username
opp_locked.is_locked = True
opp_locked.save()
# This is what has to be returned, but i need context on the other page.
return render(request, 'opportunity/detalji/poziv.html',
{'opp': opp, 'locked': opp_locked})
else:
# This return has the context that i need(from the first opp_locked variable)
return render(request, 'opportunity/opp_optika.html', {'locked_test': opp_locked})
I can provide more code, but i think that it's not important for this type of question, because all of the logic is happening inside the lock finction, and last two returns.
I just had a quick overview of your snippet sorry if this not help you but you need to review it a little bit.
You call DoesNotExist on an instance of a Locked model
if opp_locked.DoesNotExist: [...]
that's not how you should use this exception.
You have a method .exists() that is available but only for Querysets.
Also if your instance does not exists you are alredy returning an Http404 response when you use get_object_or_404() method.
And perhaps you should avoid sharing primary keys between instances and replace them with models.OneToOneField (OneToOnefield)
Since i got no answers, i added a new field, is_locked, into my Locked model and that solved it.

mongoengine know when to delete document

New to django. I'm doing my best to implement CRUD using Django, mongodb, and mongoengine. I'm able to query the database and render my page with the correct information from the database. I'm also able to change some document fields using javascript and do an Ajax POST back to the original Django View class with the correct csrf token.
The data payload I'm sending back and forth is a list of each Document Model (VirtualPageModel) serialized to json (each element contains ObjectId string along with the other specific fields from the Model.)
This is where it starts getting murky. In order to update the original document in my View Class post function I do an additional query using the object id and loop through the dictionary items, setting the respective fields each time. I then call save and any new data is pushed to the Mongo collection correctly.
I'm not sure if what I'm doing to update existing documents is correct or in the spirit of django's abstracted database operations. The deeper I get the more I feel like I'm not using some fundamental facility earlier on (provided by either django or mongoengine) and because of this I'm having to make things up further downstream.
The way my code is now I would not be able to create a new document (although that's easy enough to fix). However what I'm really curious about is how I would know when to delete a document which existed in the initial query, but was removed by the user/javascript code? Am I overthinking things and the contents of my POST should contain a list of ObjectIds to delete (sounds like a security risk although this would be an internal tool.)
I was assuming that my View Class might maintain either the original document objects (or simply ObjectIds) it queried and I could do my comparisions off of that set, but I can't seem to get that information to persist (as a class variable in VolumeSplitterView) from its inception to when I received the POST at the end.
I would appreciate if anyone could take a look at my code. It really seems like the "ease of use" facilities of Django start to break when paired with Mongo and/or a sufficiently complex Model schema which needs to be directly available to javascript as opposed to simple Forms.
I was going to use this dev work to become django battle-hardened in order to tackle a future app which will be much more complicated and important. I can hack on this thing all day and make it functional, but what I'm really interested in is anyone's experience in using Django + MongoDB + MongoEngine to implement CRUD on a Database Schema which is not vary Form-centric (think more nested metadata).
Thanks.
model.py: uses mongoengine Field types.
class MongoEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, VirtualPageModel):
data_dict = (o.to_mongo()).to_dict()
if isinstance(data_dict.get('_id'), ObjectId):
data_dict.update({'_id': str(data_dict.get('_id'))})
return data_dict
else:
return JSONEncoder.default(self, o)
class SubTypeModel(EmbeddedDocument):
filename = StringField(max_length=200, required=True)
page_num = IntField(required=True)
class VirtualPageModel(Document):
volume = StringField(max_length=200, required=True)
start_physical_page_num = IntField()
physical_pages = ListField(EmbeddedDocumentField(SubTypeModel),
default=list)
error_msg = ListField(StringField(),
default=list)
def save(self, *args, **kwargs):
print('In save: {}'.format(kwargs))
for k, v in kwargs.items():
if k == 'physical_pages':
self.physical_pages = []
for a_page in v:
tmp_pp = SubTypeModel()
for p_k, p_v in a_page.items():
setattr(tmp_pp, p_k, p_v)
self.physical_pages.append(tmp_pp)
else:
setattr(self, k, v)
return super(VirtualPageModel, self).save(*args, **kwargs)
views.py: My attempt at a view
class VolumeSplitterView(View):
#initial = {'key': 'value'}
template_name = 'click_model/index.html'
vol = None
start = 0
end = 20
def get(self, request, *args, **kwargs):
self.vol = self.kwargs.get('vol', None)
records = self.get_records()
records = records[self.start:self.end]
vp_json_list = []
img_filepaths = []
for vp in records:
vp_json = json.dumps(vp, cls=MongoEncoder)
vp_json_list.append(vp_json)
for pp in vp.physical_pages:
filepath = get_file_path(vp, pp.filename)
img_filepaths.append(filepath)
data_dict = {
'img_filepaths': img_filepaths,
'vp_json_list': vp_json_list
}
return render_to_response(self.template_name,
{'data_dict': data_dict},
RequestContext(request))
def get_records(self):
return VirtualPageModel.objects(volume=self.vol)
def post(self, request, *args, **kwargs):
if request.is_ajax:
vp_dict_list = json.loads(request.POST.get('data', []))
for vp_dict in vp_dict_list:
o_id = vp_dict.pop('_id')
original_doc = VirtualPageModel.objects.get(id=o_id)
try:
original_doc.save(**vp_dict)
except Exception:
print(traceback.format_exc())

Set default value for dynamic choice field

I have a form that asks the user to enter in their zip code. Once they do it sends them to another form where there is a field called 'pickup_date'. This gets the value of the zip from the previous field and gets all of the available pickup_dates that match that zip code into a ChoiceField. I set all of this within the init of the model form.
def __init__(self,*args,**kwargs):
super(ExternalDonateForm,self).__init__(*args,**kwargs)
if kwargs:
zip = kwargs['initial']['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
elif self.errors:
zip = self.data['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
The problem I have is when there are other errors on the form. I use the elif self.errors to regenerate the possible choices but it doesn't default to the original selected option. It goes back and defaults to the first choice. How can I make it so it's default option on form errors is what was originally posted?
Change self.fields['pickup_date'] to self.fields['pickup_date'].initial and see if that helps.
I got it to work after playing around for a while. Above, I was setting all the dynamic choices with a get_dates() function that returned a tuple. Instead of doing that I returned a field object like this using a customized ModelChoiceField instead of a regular ChoiceField....
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.date.strftime('%a %b %d, %Y')
Dates function
def get_dates(self,zip):
routes = Route.objects.filter(zip=zip).values_list('route',flat=True)
pickups = self.MyModelChoiceField(queryset = PickupSchedule.objects.filter(
current_count__lt=F('specials'),
route__in=routes,
).order_by('date')
)
if not pickups:
pickups = (('----','No Pickups Available At This Time'),)
return pickups
in the init i set the value for self.fields['pickup_date'] like so..
self.fields['pickup_date'] = self.get_dates(zip)

submitting form results into db - django

i created a form to save a post into db for my blog project. I've designed index page. now i am tryin to create a form to create new posts. before that i was using ' manage.py shell'
here is my view :
def addpost(request):
form = addForm()
if request.method=="POST":
titleform = request.POST['title']
bodyform = request.POST['body']
checkform = request.POST['isdraft']
if form.is_valid():
n = Post(title = titleform, body = bodyform, isdraft=checkform)
n.save()
return HttpResponseRedirect('/admin/')
else:
pass
return render(request,'userside/add.html',{'form':form,})
my model.py:
class Post(models.Model):
title = models.CharField(max_length = 100)
body = models.TextField()
slug = AutoSlugField(populate_from='title',unique=True)
posted = models.DateField(auto_now_add=True)
isdraft = models.BooleanField()
def __unicode__(self):
return self.title
#permalink
def get_absolute_url(self):
return ('view_blog_post',None, {'postslug':self.slug})
class addForm(forms.Form):
title = forms.CharField(max_length=100)
body = forms.CharField(widget=forms.Textarea)
isdraft = forms.BooleanField()
if i submit form as 'isdraft' field is False(unchecked) ; it gives error like:
MultiValueDictKeyError at /admin/addpost/
"Key 'isdraft' not found in "
and if i submit the form as 'isdraft' field is True(checked) ; it gives nothing. just refreshing form. no adding data into db.
i am doing sth wrong..
thank you
edit : Dmitry Beransky's answer worked for checkbox error. but it still doesnt add any data into db. just refreshes the form.
The whole point of using a form is that it takes care of validation and cleaning, that is converting values to the proper data types. That's why you should be accessing form.cleaned_data rather than reques.POST, and you should be doing it inside the if form.is_valid() check.
Edit
I've just noticed that you're never passing request.POST to the form. So form.is_valid() will never be true.
Please go back and read the documentation about using a form in a view.
If a checkbox is not checked in your HTML form, it's name/value is not going to be included in the data that the browser sends to your server. Which meanst that the request.POST dictionary is not going to contain an entry for 'isdraft' which in turn will cause a key error when you try to read the isdraft value. A solution is to change the way you read the value from the posted data to:
checkform = request.POST.get('isdraft', False)
rather than throw an error if isdraft isn't found in the dictionary, this will set checkform to False (the default value in case of a missing key)
Maybe your form does not validate at all. Have you checked if your code even reaches those lines after the if form.is_valid() statement ? If they do, what you've done there is right and should create the db row for your new entry, though you could have used
Post.objects.create(....) , and that would have taken away the need for calling the method save().
Some points though:
instead of checking for request.POST , check for request.method == 'POST' , cause there might be a post which has an empty POST dict ( in case no arguments have been submitted ), in that case request.POST fails to provide the right check .
see the docs for more info : request.POST
instead of using request.POST['var_name'] , use request.POST.get('var_name', 'default_value') , cause doing this like request.POST['var_name'] might result in some exceptions ( in case for example the argument is not provided , like what happened for your checkform variable )
Try accessing those variables through form.cleaned_data
and finally , you don't need the else statement in the end , just use the indentation :)

Need clarification on using Django 1.4 Form Wizards, specifically pre-filling and saving

We are building a wizard using Django 1.4's new form wizard functionality.
The docs on this are very terse and we can't find any advanced examples. We are using a named step wizard (needed to support a listview/datagrid we use) and a session backend.
The wizard is meant to edit roles and linked rights and is built to provide both add and edit functionality. We do this by asking the user in the first step if he/she wants to add or edit.
The next step depends on that choice;
If the user wants to edit, there is a search screen, followed by a listview/datagrid that displays results. The user can then select one of the results and goes to a details-screen, followed by a FilteredSelectMultiple page, allowing him/her to link rights to this role.
If the user wants to add a new role, the search and results screens are skipped and the user goes directly to the details screen, followed by the link-screen.
It all works pretty well, using a condition_dict in urls.py, but we are wondering a couple of things about the general functionality:
When a specific pre-existing role is selected, how can we fill the details and the link-screen with the corresponding data?
Do we instantiate a roles-object and pass it somehow to the two forms, if so, where do we instantiate it and do we need to do that for every form separately (which seems a bit over the top)?
When saving, is it common practice to create another instance of a role object, add the form data to it and save, or can we re-use the object used in the forms somehow?
We have tried overloading get_form_instance to return instances of roles, and we have looked at instance_dict in the docs, but it feels like the wrong approach and there are no examples to be found online, and we're not even sure these are used to pre-fill data or even if we're on the right track.
Logically, I would say in the step that selects an existing role, I need to fill the wizard-variables using an instance of the chosen object, and these get displayed in the forms. At the end of the wizard we reverse the process and get all data from the wizard-variables and add them to a newly instantiated roles-object and save it. Ideally this instance will determine itself if it needs to perform an INSERT or an UPDATE, depending on whether or not the promary key is filled.
If anyone can provide an example, or a nudge in the right direction, it would be very much appreciated.
The code of the wizardview class in views.py is below:
class RolesWizard(NamedUrlSessionWizardView):
def get_template_names(self):
# get template for each step...
if self.steps.current == 'choice':
return 'clubassistant/wizard_neworeditrole.html'
if self.steps.current == 'search':
return 'clubassistant/wizard_searchrole.html'
if self.steps.current == 'results':
return 'clubassistant/wizard_pickrole.html'
if self.steps.current == 'details':
return 'clubassistant/wizard_detailsrole.html'
elif self.steps.current == 'rights':
return 'clubassistant/wizard_roles.html'
def get_context_data(self, form, **kwargs):
# get context data to be passed to the respective templates
context = super(RolesWizard, self).get_context_data(form=form, **kwargs)
# add the listview in the results screen
if self.steps.current == 'results':
# get search text from previous step
cleaned_data = self.get_cleaned_data_for_step('search')
table = RolesTable(Roles.objects.filter(
role_name__contains=cleaned_data['searchrole'])
)
RequestConfig(self.request, paginate={
"per_page": 4,
}).configure(table)
# add the listview with results
context.update({'table': table})
# add a role instance based on the chosen primary key
if self.steps.current == 'rights':
cleaned_data = self.get_cleaned_data_for_step('results')
role_id = cleaned_data['role_uuid']
role = get_object_or_404(Roles, pk=role_id)
context.update({'role': role})
return context
def done(self, form_list, **kwargs):
# this code is executed when the wizard needs to be completed
# combine all forms into a single dictionary
wizard = self.get_all_cleaned_data()
if wizard.get("neworeditrole")=="add":
role = Roles()
else:
role = get_object_or_404(Roles, pk=wizard.get("role_uuid"))
# many-to-many rights/roles
role.role_rights_new_style.clear()
for each_right in wizard.get('role_rights_new_style'):
RightsRoles.objects.create(role=role, right=each_right,)
# other properties
for field, value in self.get_cleaned_data_for_step('details'):
setattr(role, field, value)
role.save()
# return to first page of wizard...
return HttpResponseRedirect('/login/maintenance/roles/wizard/choice/')
For future googlers:
I had some success with using get_form() because it is called before a form is rendered. Start with a couple of ModelForms:
class Wizard1(models.ModelForm):
class Meta:
model = MyModel
fields = ('field0', 'model0')
class Wizard2(models.ModelForm):
class Meta:
model = MyModel
excludes = ('field0', 'model0')
Then, in your SessionWizardView:
class MyWizard(SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super(ExtensionCreationWizard, self).get_form(step, data, files)
if step is not None and data is not None:
# get_form is called for validation by get_cleaned_data_for_step()
return form
if step == "0":
# you can set initial values or tweak fields here
elif step == "1":
data = self.get_cleaned_data_for_step('0')
if data is not None:
form.fields['field1'].initial = data.get('field0')
form.fields['field2'].widget.attrs['readonly'] = True
form.fields['field3'].widget.attrs['disabled'] = True
form.fields['model1'].queryset = Model1.objects.filter(name="foo")
return form
The action is all in step 1. You request validated data from step 0 (which triggers another call to get_form() for step 0, so be careful) and then you can access any values that were set in step 0.
I threw in a couple of examples of settings you can change on the fields. You can update a queryset to limit the values in a ChoiceField, or re-display a value again but make it read-only. One caveat I noticed... readonly does not work on ChoiceField. You can make it disabled, but then the value is not propagated when you submit the form.
Let's see if I can help. I did a form wizard that adds steps depending on the answers. At each step I save all forms in a session variable, like so:
def process_step(self, request, form, step):
request.session['form_list'] = self.form_list
request.session['initial'] = self.initial
Then, each time that view is rendered, I instantiate a new form wizard with all the previous data:
def dynamic_wizard(request):
if not request.session.get('form_list'):
form = Wizard([Form1])
else:
form = Wizard(request.session.get('form_list'), initial = request.session['initial'])
return form(context=RequestContext(request), request=request)