Django ManyToMany field using through with relational value - django

I have a model with many to many field, the field it linked through a table with Boolean value.
I got this in different scenario, lets take user as the main model, interest as the many to many field and user_interest model as the through model, inside user_interest I have a Boolean field which I want user to specify when selecting each of his many to many value,
At the moment am stuck trying to find an appropriate form interface to cater for my need, currently am using MultipleSelect widget which only show selection of interest, is there any example for field control that would allow me to choose the interest and specify the value of the Boolean field? If not, what is best practice to follow in my situation?
Best regards,
Update question with code samples
class Interest(models.Model):
name = models.CharField(_('Name'), max_length=200, )
class UserInterest(models.Model):
interest = models.ForeignKey(Interest, )
user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL'), )
join_mail_list = models.BooleanField(default=False)
class User(models.Model):
name = models.CharField(max_length=100)
interest = models.ManyToManyField(Interest, through='UserInterest', related_name='user_interest')
Now based on the models above, when I try to create a ModelForm from User model, I will get interest as MultiSelect field which is perfectly correct. But, in my case, I want the form to collect user interests + for each interest if he wants to join_mail_list for that particular interest. Please advise?

I'll just assume that you want a list of all interests with a checkbox next to them.
Let's assume those models:
class Ressource(models.Model):
name = models.CharField(max_length=255)
class Ressource2Village(models.Model):
ressource = models.ForeignKey(Ressource)
village = models.ForeignKey(Village)
amount = models.FloatField()
class Village(MapObject):
name = models.CharField(max_length=255)
And this form:
class Ressoure2VilllageForm(forms.Form):
name = forms.CharField()
# you would use a BooleanField
value = forms.IntegerField()
Now you can create a formset (https://docs.djangoproject.com/en/1.5/topics/forms/formsets/):
from django.forms.formsets import formset_factory
r2v_formset = formset_factory(Ressoure2VilllageForm)
# use boolean for your case, if you want to edit these values later use n2m to initialize value
# [{'name': r2v.ressource.name, 'value': r2v.value} for r2v in your_village.ressource2village.all()]
init_values = [{'name': r.name, 'value': 0} for r in Ressource.objects.all()]
r2v_formset = r2v_formset(initial=init_values)
Which in your template will give you:
<label for="id_form-0-name">Name:</label><input type="text" name="form-0-name" value="Kohle" id="id_form-0-name">
<label for="id_form-0-value">Value:</label><input type="text" name="form-0-value" value="0" id="id_form-0-value">
<label for="id_form-1-name">Name:</label><input type="text" name="form-1-name" value="Eisenerz" id="id_form-1-name">
<label for="id_form-1-value">Value:</label><input type="text" name="form-1-value" value="0" id="id_form-1-value">
<label for="id_form-2-name">Name:</label><input type="text" name="form-2-name" value="Golderz" id="id_form-2-name">
[...]
The association to the specific user can be done during processing under consideration of his authentication details.
Displaying, validating and processing this should be pretty straightforward, feel free to ask follow up questions.

The question is kind of general, but I'd say that since it's a boolean field, instead of one multiselect field, you have two - one for "Things I'm interested in..." (True) and one for "Thing's I'm not interested in..." (False). Both fields would be multiselect from the same list of items, but the seperation allows the user to apply the boolean field without even noticing. Also, I strongly recommend jQuery UI autocomplete, seems like the best choice here.
Updated answer:
So seeing your code helps clear a few things. The way I would do it is like I suggested in the first place but with a little tweak.
Having a newsletter checkbox for each 'interest' is clumsy, because it requires the user to check the 'interest' twice - once because he's interested in it and once because he wants emails about it. So you have to automatically assume that the user wants to get an email for the subjects he is interested in, but allow them to prevent it if they want.
With that in mind, you build two long text inputs, and implement both of them with a tagging system (here's a good one, here are some more). The first input is "I'm interest in", and the second is "I want to recieve emails about...". Then you add an event listner with jQuery that makes sure that for every tag added to the first input it is automatically added to the second one as well. Then, if the user doesn't want to get emails for a specific subject (or all of them) he can easily take it off the second input (which doesn't have any effect on the first one).
To give you an example, here's a jsfiddle that shows how to make that happen with the chosen-jQuery library. I used Countries and Shipping because I had a pre-made list at the chosen library website, so I just copied it from there. As you can see, it's a pretty simple jQuery code, which you can easily applicate for any other tagging system (and many thanks to the fine Raúl Juárez who helped me work that out myself):
$(".chosen-select").chosen({disable_search_threshold: 10});
$('#countries').on('change', function(e) {
var selected = $(this).val();
$.each(selected, function(index, value) {
$('#shipping option[value="'+value+'"]:first').prop('selected',true);
});
$(".chosen-select").trigger('chosen:updated');
});

Related

Django: Generate form from dictionary

I'm currently working on a django-app where users have the option to select synonyms for a select number of words. These words are then replaced all over the website by these synonyms
these synonymes are defined as a separate model:
class TransportSynonyms(models.Model):
user= models.ForeignKey(user)
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
This process is done in part with template tags, and as such the number of words that can be 'synonymed' is limited. For example, the following words can be replaced by synonymes:
'train', 'plane'.
And appear like so in the html template:
{% get_trans "train" %}
{% get_trans "plane" %}
I now want to give user the ability to define synonymes for themselves, without the admin view. I created a few pages using a ListView as an overview (so the users could see which words they can change) with individual buttons that led to EditViews.
However, I'm since user have no synonyms linked to them by default, the ListView appears empty. I could solve that by passing a list from the view, but that list wont have the required '.id' values and would be worthless for linking to the EditViews.
My listview currently looks like the following table:
Original | Synonym
---------------------------
train | train (button to editview)
plane | aircraft (button to editview)
The buttons require an ID value
href="{% url 'synonym_edit' pk=synonym.id %}"
I have to find a way to fill the ListView with the 'synonymable' words and provide links to a DetailView (for synonyms might not yet exist).
I thought about dropping the ListView all together and instead pass a dictionary to a form. By default both the key and the value will be the same word
({'train' : 'train', 'plane' : 'plane'})
and a form would then be generated form this dictionary, allowing users to change the value by having the dictionary value be shown as a text input.
Its a bit more limited (and more cumbersome) than my original plan, but I think this might work. Problem is that I've only really worked with modelforms before, and I am currently stuck on having the form be generated from the dictionary. Could anyone here point me to the right direction?
Regards,
Jasper

Edit css files from django admin

My client wants to edit the css files of the site from django admin.
Is there any way to do it ? .
Basically what they want is,to be able to change the color,font etc of the data in the front end from django admin interface.
The best thing would be to just let him edit the css file itself. CSS is, in essence, a rather flexible tool, so writing a way to manage it is rather tough (and really, overkill). It's already easy to pick-up, and any nice editor like sublime or notepad++ would probably be easier and more natural than whatever you'll build using the admin site. Also, by building a simple way to control css, your client will probably start asking for more and more flexibility until you find yourself building an entire cms (trust me, I've been there myself).
What's more, your client probably only wants to manage small aspects or details of the site. Recently I had a project where I allowed my users to style their display of my application. The way I did it was to create a UserDesign model which extended the base User model and kept very specific css data. Something like this:
class UserDesign(models.Model):
user = models.OneToOneField(User)
background_color = models.CharField(max_length=15)
font_color = models.CharField(max_length=20, choices=COLORS)
theme = models.CharField(max_length=20, choices=THEMES)
Meaning, they didn't control the entirety of the css, but they did get to choose the background color and some other information. It's a very neat addition to any website. However, if you are bent over doing it the hard way, I'd do something like this:
class Selector(models.Model):
name = models.CharField(max_length=30)
def get_template(self):
attrs = [a.join() for a in self.attr_set.all()]
return """ %s { %s } """ % ( self.name, ';'.join(attrs) )
class Attr(models.Model):
key = models.CharField(max_length=30)
value = models.CharField(max_length=30)
selector = models.ForeignKey(Selector)
def join(self):
return ': '.join(self.key, self.value)
I chose 30 as the max_length completely arbitrarily (you might need it longer), and you can use a TabularInline to make each selector easy to manage. Then you can easily use different css definitions inside your templates themselves:
<style>
{% for selector in selectors %}
{{ selector.get_template }}
{% endfor %}
</style>
Of course, the Selector model would probably need another field called 'template' or 'view' or something, to link it to a certain html file, though at this point it quickly start devolving into building your own cms (which, as mentioned before, is quite a headache that not wanting to edit a text file just doesn't justify)
A third viable option is to create a view with a code-editor, and just let your client edit his css through the web page. There's more than enough client-side plugins out there, like ace or codemirror (and of course, limit that view to administrators, which very simple to do).

Customizing Django.contrib.comments honeypot

I'm using Django's standard comment system and I would like to extend its anti-spam honeypot capability.
I thought of changing the default "name" and "id" of the field to something more alluring for spam-bots such as "website". I checked the html and this looks like this:
<p style="display:none;">
<label for="id_honeypot">Never send a human to do a machine's job</label>
<input type="text" name="honeypot" id="id_honeypot" />
</p>
Am I correct in thinking that changing the defaults of this element would boost its anti-spam capabilities? I tried modifying it in the django/contrib/comments/forms.py like this:
class CommentForm(CommentDetailsForm):
#use to be honeypot = forms.CharField(...
website = forms.CharField(required=False,
label=_('Never send a human to do a machines job')
def clean_honeypot(self):
"""Check that nothing's been entered into the honeypot."""
value = self.cleaned_data["website"]
if value:
raise forms.ValidationError(self.fields["website"].label)
return value
And this successfully changes the name and id in the html generated by django BUT then the whole mechanism stops working - I tried populating this invisible field, submitted and the comment was added.
I have a few other ideas as well, but first I'd really like to get this working - is it possible to modify the default honeypot name and id AND have it working like it should?
P.S I believe a more elegent way of doing this would be to extend django.contrib.comments and code the modification there instead of working on actual django code - what would be the best way of accomplishing this?
Given a bit more time to tinker around I found the answer to both of my questions:
In order to modify the standard honeypot or to create your own, you have to extend the CommentForm class by adding a clean_NAME_OF_HONEYPOT function as well as a NAME_OF_HONEYPOT variable both of which look similar to the standard ones and you also have to override the security_errors function to include the name of your new/modified honeypot in the dictionary.
The best way to do this is to create your custom comments app as described here: https://docs.djangoproject.com/en/dev/ref/contrib/comments/custom/ .
I hope this answer helps anyone else in my situation.

Alternative form for Django's `ChoiceField` that can handle thousands of entries

I have a form with a ChoiceField in it. It is rendered to the user as a dropdown box.
Problem is, I have thousands of entries in this field, which is causing the page to (a) load very slowly and (b) be sluggish.
I want an alternative widget, instead of Select, that could handle more than 10,000 choices.
Something like the admin's raw_id_fields would be good (if only it were usable in general forms...) but I'm open to ideas.
If autocomplete is an option for your UI you can take a look to django-simple-autocomplete:
App enabling the use of jQuery UI autocomplete widget for
ModelChoiceFields with minimal configuration required.
EDITED (reply OP comment)
I have not tested this solution, but digging documentation and source it seems that not all data is loaded at a time:
The ability to specify an URL for the widget enables you to hook up to
other more advanced autocomplete query engines if you wish.
Source code:
def get_json(request, token):
"""Return matching results as JSON"""
...
di = {'%s__istartswith' % fieldname: searchtext} # <- look here!
items = queryset.filter(**di).order_by(fieldname)[:10]
Widget source code
$("#id_%(name)s_helper").autocomplete({
source: function(request, response){
$.ajax({ # <-- look here
url: "%(url)s",
data: {q: request.term},
success: function(data) {
I don't know what is the raw_id_fields but why not use a model to store all your choices ?
class Choice(models.Model):
value = models.CharField()
class MyModel(models.Model):
choice = models.ForeignKey(Choice)
It would then be easy to select it if you want to display only 20 at a time for example.
Based on this comment (which really, you should have included in your question):
Let me clarify my task: I have 10,000 users. I have a form in which
you choose a user. You need to be able to choose any user you want.
You can't just load 20, because then you won't be able to choose the
other 9,980 users.
If you want something built-in, you can use the FilteredSelectMultiple widget from django.contrib.admin.widgets, which puts a filter on your select.
You should also cache the results of the 10,000 users so you don't hit your db everytime. This is what is causing your delay, not the number of users (which is tiny, for practical performance problems).

is there a way to use django generic views and some smart urlpatterns for quick ordering/sorting of queries?

let's assume I have a django model like this:
class Event(CommonSettings) :
author = models.ForeignKey(User)
timestamp = models.DateTimeField(auto_now_add=True)
event_type = models.ForeignKey(Event_Type, verbose_name="Event type")
text_field = models.TextField()
flag_box = models.BooleanField()
time = models.TimeField()
date = models.DateField()
project = models.ForeignKey(Project)
now, by default, I have a view where I sort all events by time & date:
event_list = Event.objects.filter().order_by('-date', '-time')
however, maybe the user wants to sort the events by time only, or by the date, or maybe in ascending order instead of descending. I know that I can create urlpatterns that match all these cases and then pass on the these options to my view, however I feel like I'm reinventing the wheel here. the django admin site can do all of this out of the box.
So here's my question: is there a clever, easy way of getting this done in a generic way, or do I have to hard code this for my models / views / templates?
and yes, I did find solutions like this (https://gist.github.com/386835) but this means you use three different projects to achieve one thing - this seems to be a too complicated solution for such a simple thing.
EDIT1:
how do I have to change the template so that I can combine multiple filters? Right now I have
Desc
Asc
but I want to allow the user to also change number of entries that get displayed. So I have:
order by date
order by name
This works all fine, but if I click on 'order by date' and then I click on 'Asc', then my previously selected order disappears. That's not what I want. I want the user to be able to combine some options but not others.
EDIT2:
unfortunately your solution doesn't work with
from django.views.generic.list_detail import object_list
and it's
paginate_by
option.
I tried:
prev
{% trans "next" %}
but the links then just don't work (nothing happens). maybe you need to do something special with "object_list"?
I don't think it's as much work as you're making it out to be - you can use variables instead of explicitly creating separate url patterns. If you look at how the django admin handles it, they tack on request variables to the url like ?ot=asc&o=2 This corresponds to sort in ascending order in by the 2nd column. Of course, if you designing a particular page, you might as well use more readable naming. So instead of numbering the categories, i'd do ?sort=desc&order_by=date and then put a regular expression in the view to match the different possibilities. Something like:
order = re.match(r"(?:date|time|name)$", request.GET['order_by'])
if request.GET['sort'] == 'desc':
order = '-' + order
results = Event.objects.filter().order_by(order)
You could instead use the regexp as a url pattern matcher as you suggested, but it's more common to let the url itself represent which part of the site you're at (i.e. website.com/events/) and the url request variables represent how that content is being displayed (i.e. ?order_by=date&sort=desc).
Hope that helps!
EDIT: For the second part of your question, use Django's templating system (which reads variables) instead of just html. There are several ways I can think of to do this, depending on personal preference and how exactly you want the UI to function (i.e. page loads with new variables anytime the user chooses a filter, or the user chooses all filter options in a form and then submits it so the page only has to reload once, etc). In this case, you could just do:
Ascending
Descending
Name
Date
Then in the view make sure your render_to_response arguments include a dictionary that looks like: {'order': request.GET['order_by'], 'sort': request.GET['sort_by'], }
Unfortunately, (and someone please correct me if I'm wrong) I don't think there's a template tag to generate a url with request.GET parameters - the url tag {% url name_of_view order_by=name sort_by=desc %} would generate "path/to/name_of_view/name/desc/", but I don't think there's a tag to generate "path/to/name_of_view?order_by=name&sort_by=desc". It would be pretty easy to write a custom tag for this though (I wouldn't be surprised if there's already one on django-snippets or something, although I just did a quick google search and didn't find anything).