Remove items with checkboxes in Django forms - python

I'm writing a form with Django. The form is a model form for a certain model, Experiment. Each Experiment has several TimeSlot models associated with it, defined with a ForeignKey('Experiment'). I'd like to have a form with the option to remove one or more TimeSlot instances from the EditExperimentForm by checking boxes.
Currently, I define all of the Checkboxes in the model by a loop in the init function in EditExperimentForm:
def __init__(self, *args, **kwargs):
super(EditExperimentForm,self).__init__(*args,**kwargs)
experiment = self.instance
for timeslot in experiment.timeslot_set.all():
self.fields['timeslot-'+str(timeslot.id)] = BooleanField(label="Remove Timeslot at "+str(timeslot.start),required=False)
And then I process them upon submission with a regular expression:
timeslot_re = re.compile(r'^timeslot-([\d]+)$')
for key in form.data.keys():
match = timeslot_re.match(key)
if match:
timeslot = TimeSlot.objects.get(id=match.expand(r'\1'))
timeslot.delete()
This is far from an elegant solution (for one thing, it makes anything but the most generic template a straight up nightmare to work with. Can anyone think of an easier way to do this?

This code isn't tested, but something like this should do it:
class MyForm(forms.Form):
# You can change the queryset in the __init__ method, but this should be a nice basis
timeslots = forms.ModelMultipleChoiceFieldqueryset=Timeslot.objects.all(), widget=forms.CheckboxSelectMultiple)
def save(self):
# make sure you do a form.is_valid() before trying to save()
for timeslot in self.cleaned_data['timeslots']:
timeslot.delete()

It may be a cleaner solution if you used a model formset for your TimeSlot objects. Have you looked at that at all?
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#id1

Related

Get variable OneToOneField in Django

Lets say I have a Form model:
class Form(models.Model):
name = models.TextField()
date = models.DateField()
and various "child" models
class FormA(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_a = models.TextField()
class FormB(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_b = models.IntegerField()
class FormC(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_c = models.BooleanField()
a Form can be one AND ONLY ONE of 3 types of forms (FormA, FormB, FormC). Given a Query Set of Form, is there any way I can recover what types of Form (A, B or C) they are?
I would need to get a better understanding of your actual use case to know whether this is a good option for you or not, but in these situations, I would first suggest using model inheritance instead of a one to one field. The code you have there is basically doing what multi-table inheritance already does.
Take a read through the inheritance docs real quick first and make sure that multi-table inheritance makes sense for you as compared to the other options provided by django. If you do wish to continue with multi-table inheritance, I would suggest taking a look at InheritanceManager from django-module-utils.
At this point (if using InheritanceManager), you would be able to use isinstance.
for form in Form.objects.select_subclasses():
if isinstance(form, FormA):
..... do stuff ......
This might sound like a lot of extra effort but IMO it would reduce the moving parts (and custom code) and make things easier to deal with while still handling the functionality you need.
You can check it by name or isinstance.
a = FormA()
print(a.__class__)
print(a.__class__.__name__)
print(isinstance(a, Forma))
outputs:
<class __main__.FormA at 0xsomeaddress>
'FormA'
True
------------------- EDIT -----------------
Ok based on your comment, you just want to know which instance is assigned to your main Form.
So you can do something like this:
if hasattr(form, 'forma'):
# do something
elif hasattr(form, 'formb'):
# do something else
elif hasattr(form, 'formb'):
# do something else
After investigating a bit I came up with this
for form in forms:
#reduces fields to those of OneToOne types
one_to_ones = [field for field in form._meta.get_fields() if field.one_to_one]
for o in one_to_ones:
if hasattr(form,o.name):
#do something
Might have some drawbacks (maybe bad runtime) but serves its purpose for now.
Ideas to improve this are appreciated

Django 'likes' - ManyToManyField vs new model

I'm implementing likes on profiles for my website and I'm not sure which would be the best practice, a ManyToManyField like so:
class MyUser(AbstractBaseUser):
...
likes = models.ManyToManyField('self', symmetrical = False, null = True)
...
or just creating a class Like, like so:
class Like(models.Model):
liker = models.ForeignKey(MyUser, related_name='liker')
liked = models.ForeignKey(MyUser, related_name='liked')
Is one of them a better choice than the other? If so, why?
thanks
The first option should be preffered. If you need some additional fields to describe the likes, you can still use through="Likes" in your ManyToManyField and define the model Likes.
Manipulating the data entries would be also somewhat more pythonic:
# returns an object collection
likes_for_me = MyUser.objects.filter(pk=1).likes
instead of:
me = MyUser.objects.filter(pk=1)
likes_for_me = Like.objects.filter(liked=me)
The second option is basically what is done internally: a new table is created, which is used to create the links between the entities.
For the first option, you let django do the job for you.
The choice is certainly more about how you want to do the requests. On the second options, you would have to query the Like models that match you model, while on the first one, you only have to request the MyUser, from which you can access the connections.
Second option is more flexible and extensible. For example, you'll probably want to track when like was created (just add Like.date_created field). Also you'll probably want to send notification to content author when content was liked. But at first like only (add Like.cancelled boolead field and wrap it with some logic...).
So I'll go with separate model.
I think the one you choose totally depends on the one you find easier to implement or better. I tend to always use the first approach, as it is more straightforward and logical, at least to me. I also disagree with Igor on that it's not flexible and extensible, you can also initiate notifications when it happens. If you are going to use the Django rest framework, then I totally suggest using the first method, as the second could be a pain.
class Post(models.Model):
like = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_like')
Then in your view, you just do this.
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def like(request, id):
signed_in = request.user
post = Post.objects.get(id=id)
if signed_in and post:
post.like.add(signed_in)
# For unlike, remove instead of add
return Response("Successful")
else:
return Response("Unsuccessful", status.HTTP_404_NOT_FOUND)
Then you can use the response however you like on the front end.

Django admin change list view disable sorting for some fields

Is there are way(s) to disable the sorting function for some fields in django admin change list so for those fields, users cannot click the column header to sort the list.
I tried on the following method, but it doesn't work.
https://djangosnippets.org/snippets/2580/
I also tired to override the changelist_view in ModelAdmin but also nothing happen.
def changelist_view(self, request, extra_context=None):
self.ordering_fields = ['id']
return super(MyModelAdmin, self).changelist_view(request, extra_context)
In the above case, I would like to only allow user to sort the list by ID.
Anyone has suggestion? Thanks.
For Django 1.7 (or the version that I last use) do not support such things. One possible dirty work-around could be defining a model class method and using that method instead of model field.
class TestClass(Model):
some_field = (.....)
other_field = (........)
def show_other_field(self):
return self.other_field
class TestClassAdmin(ModelAdmin):
list_display = ("some_field", "show_other_field")
Since show_other_field is a model class method, django do not knows how to sort (or process) the return result of that method.
But as I said, this is a dirty hack that might require more processing (and maybe more database calls) according to use-case than displaying a field of a model.
Extra: If you want to make a model method sortable, you must pass admin_order_field value like:
def show_other_field(self):
return self.other_field
show_other_field.admin_order_field = "other_field"
That will make your model method sortable in admin list_display. But you have to pass a field or relation that is usable in the order_by method of database api.
TestClass.objects.filter(....).order_by(<admin_order_field>)

Python / Django - Edit Model Forms After Creation

I have a strong background in PHP / ZEND and I'm now using learning Python / Django. In Zend you can take a form element object and edit it pretty much at any time. This is great because you can take a form object and make small alterations to it on demand without created a completely new form object. I am trying to do this is in Django.
I have a form. Lets call it vote. This form may need a different widget applied in a different view method. I don't want to recreate the entire form with such a small change...
ie
form = VoteForm(initial={})
## then something like
form.field.widget = newWidget
Basically,
I want to modify a model form element after the object has been created inside the views...
You answered your own question: that's (almost) exactly how you do it!
# tested on 1.2.3
form = VoteForm(initial={})
form.fields['field_name'].widget = forms.HiddenInput() # make sure you call widget()
form.as_p() # shows new widget
Another way is to override the form's init() method, something like:
class VoteForm(forms.Form):
myfield = ...
def __init__(self, hide_field=False, *args, **kwargs):
super(VoteForm, self).__init__(*args, **kwargs)
if hide_field:
self.fields['myfield'].widget = ...
form = VoteForm(hide_field=True, initial={})
I personally prefer this method, keeps all the form logic in one place instead of spread around. Assuming your forms and views are in separate files, means you won't have to do multiple 'from django import forms' to get the widgets in the views.

Django 1.1 FormWizard, Dynamically extend form

I am trying to create a multipage form, where the number of field elements on the second page is defined by the answers given on the first.
I have a formWizard set up and my understanding is that I need to use process_step() to alter the setup for the next page. I can either extend an existing form definition to add more elements, or merge 2 or more form definitions together to produce the correct number of form elements, but i have no idea how to do this.
Eg
Page 1 - Select interested subjects:
Page 2 - for each subject: ask relevant questions. Questions are defined as seperate forms in application, but need to be shown on one page, or merged into a single form.
Any help much appreiciated.
Spender
Spender,
At least at the moment I don't know a way of merging multiple forms onto one page in a FormWizard. In django 1.2 you will be able to include FormSets as steps in FormWizards (as per this ticket) but those only deal with multiple copies of identical forms, not compilations of many forms. But there is a way to do what you ask:
from django.contrib.formtools.wizard import FormWizard
from django import forms
class SubjectForm(forms.Form):
subjects = forms.MultipleChoiceField(choices = (('language', 'language'),
('sport','sport')))
class RelatedQForm(forms.Form):
"""Overload the __init__ operator to take a list of forms as the first input and generate the
fields that way."""
def __init__(self, interested_subjects, *args, **kwargs):
super(RelatedQForm, self).__init__(*args, **kwargs)
for sub in interested_subjects:
self.field[sub] = forms.CharField(label = "What do you think about %s" % subject)
class SubjectWizard(FormWizard):
def done(self, request, form_list):
process_form_list(form_list)
def process_step(self, request, form, step):
if step == 1:
chosen_subs = form.cleaned_data['subjects']
self.form_list[1] = RelatedQForm(chosen_subs)
With this code you instantiate your FormWizard as you normally would in the view and then let the wizard class take care of everything behind the scenes.
The general idea is to overload the init class of a "RelatedQForm" to dynamically alter the fields. This code snippet was taken from here. You can make the processing within the init operator as complex as you'd like, read "include the fields from your forms as if-elif blocks inside the for-loop" ... you could probably even figure out a way to strip the fields from your current forms programatically, I'd have to see them to figure it out though.
Your "process_form_list" function will need to loop over the fields using something like:
for field, val in form.cleaned_data.items():
do_stuff
Hope this gets you on your way :)
I don't think the
self.form_list[1] = RelatedQForm(chosen_subs)
part works. I always get the error message:
object is not callable
It seems to be like form_list only accepts RelatedQForm (the name of the form), not an instance of it.

Categories