Django multiple choice and multi-select field in model and form - django

I am learning Django and have hit an inssue that I don't know what to Google for. I have the standard User model and a Profile model that points into the User model via a OneToOne field. That part plus the profile's Form and the CBVs all work.
A profile holder can have one or more professional affiliations. This is not for a union but my Dad, for example, as a welder, was affiliated with: "The Iron Workers", "Steam Fitters", "Boiler Makers", "Pipe Fitters" and a couple other specific unions that used welders. So if I want the profile and form to allow multiple choices and multiple selected results for affiliations then I see that you might use
class Profile(models.Model):
GROUPS = (
('IW', 'Iron Workers'),
('PF', 'Pipe Fitters'),
...
)
affiliation = models.CharField(
max_length=3,
choices=RANK,
default='U',
blank=True,
null=True,
)
...
In the associated form it looks like you use a
GROUPS = (
('IW', 'Iron Workers'),
('PF', 'Pipe Fitters'),
...
)
affiliation = forms.MultipleChoiceField(
choices=GROUPS
)
What if I want a db table of these choice items instead of tuples? If I have to add to a list of choices then editing the code might not be the most convenient response. Maybe the list of groups should be in the Django admin area.
How do I tell the model and form to look in a separate db table for the possible values and then how do I modify the model and form to allow for zero or more group affiliations?

You need to create a many-to-many relationship to a new model, let's call it Skill, that represents the available skills that can be dynamically created.
class Profile(models.Model):
# ...
skills = models.ManyToManyField(
to='myapp.Skill', # use a string in the format `app_name.model_name` to reference models to avoid issues using the model before it was defined
related_name='user_profiles', # the name for that relation from the point of view of a skill
)
class Skill(models.Model):
name = models.CharField(max_length=255)
abbreviation = models.CharField(max_length=3)
Then in your form you can just add that many-to-many field skills. It will by default be a multiple choice field in the django admin.
The relation can have zero to n entries for each pair of profile and skill. In other words, every user can have zero to n skills and vice versa.
To allow for a default value you will have to write a custom migration that creates that default entity of a Skill to make sure it is in the database at runtime.

In my case, I solved this way with a select2 list option.
It returns in the view a list of option selected.
FORMS
class PrecoattivoForm(forms.Form):
entrate = AccertamentiEnte.objects.all().values("entrata_provvedimento").distinct()
lista = []
for i in entrate:
lista.append([i.get('entrata_provvedimento'),i.get('entrata_provvedimento')])
entrata_accertamento= forms.MultipleChoiceField(choices=lista, required=False)
TEMPLATE
<div class="col-xl-10">
<selectclass="js-example-placeholder-multiple col-sm-12"
multiple="multiple"
name="{{ form.entrata_accertamento.name }}"
id="{{ form.entrata_accertamento.id_for_label }}">
{% for entrata in form.entrata_accertamento %}
<option {% if form.name.value != None %}value="
{{form.entrata_accertamento.value|upper }}"{% endif %}>{{ entrata }}</option>
{% endfor %}
</select>
</div>

Related

Django how to select with less queries in a many to many relationship

I have the following models,
class TblMaterials(models.Model):
name = ...
....
class TblCategoris(models.Model):
name = ...
....
class TblMaterialCategories(models.Model):
tbl_categories = models.ForeignKey(TblCategories, blank=True, null=True)
tbl_materials = models.ForeignKey('TblMaterials', blank=True, null=True)
and in my home page i want to print all the materials and related material category within. Obviously there could be some materials without any categories.
in my view i try something like:
TblMaterials.objects.all().select_related('tblmaterialcategories_set')
and in templates:
{%for mat in materials%}
{{mat.name }}
{%for cat in mat.tblmaterialcategories_set.all %}
{{cat.tbl_categories.name}} ,
{%endfor%}
{%endfor%}
I dont think select_related works in that _set item.
I want to achieve something like that without making queries for each item. If I can add another field to material query set and access it like mat.categories and for loop in it, it is also appriciated.
What could be the best way to display all materials and their categories?
Thanks.
Select related can be used for the foreign keys which are in the current model. For an example:
class TblMaterials(models.Model):
name = ...
....
Foreign Key: TblMaterialCategories
While calling the TblMaterial model , if you want to use select related with TblMaterialCategories
TblMaterials.objects.all().select_related('tblmaterialcategories')
It will work.

How do I get the foreign key values of a form in a template?

So Im trying to create a similar panel like django admin and while I have my tables set up with query information and filters, Im trying to copy the "editable" option by creating fields that can be edited in the table. My problem is lets say I have models:
class model1(model.Models):
name = CharField(max_length = 20)
phone = IntegerField(max_length = 20)
def __unicode__(self):
return (#I Just return the name and phone number as a string here)
class model2(model.Models):
name = ForeignKeyFeild(model1)
team = CharField(max_length = 20)
If I set up a modelformset factory like so in a view:
qset = model2.objects.all()
fset = modelformset_factory(model2)
form = fset(queryset = qset)
In templates how can I show the value of 'phone' from model1?
When I render the form in the template:
{% for f in form %}
{{f.team.value}} # this gives me the value without an input field
{{f.name.phone.value}} # this however renders nothing and that's what I'm trying to find out
{%endfor%}
Also How can I make it so that {{f.name.value}} shows me the unicode string and not the actual numerical value (ID)?
How can I make the {{f.name.phone}} which is information from the table where it holds a foreign_key show?
If not is there a better way of doing this?
The original model object is available as the instance attribute of the form:
{% for f in form %}
{{ f.instance.team }}
{{ f.instance.name.phone }}
{% endfor %}

"Other side" of a ForeignKey relation

I read many times this and this tutorials, but I cant understand, how to do the following:
models:
class Car(models.Model):
field1
field2
field3
class CarOptions(models.Model):
car = models.OneToOneField(Car, primary_key=True)
field4
field5
class CarPictures(models.Model):
car = models.ForeignKey(Car)
field6
field7
So, I need get all information about the car in one sql-query. How it wrote in docs:
car = get_object_or_404(Car, pk=car_id)
But here is a strange (it discribes like "other side" of a ForeignKey relation) poll.choice_set.all, that doesnt works with my code. Some copy-past code, sorry, there is no links in docs:
# Give the Poll a couple of Choices. The create call constructs a new
# choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
# Create three choices.
>>> p.choice_set.create(choice='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0)
# Choice objects have API access to their related Poll objects.
>>> c.poll
<Poll: What's up?>
# And vice versa: Poll objects get access to Choice objects.
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
I havent got choice_set.all(). I add all information from the admin interface. With foreign keys all works well, but I need do a few sql-querys, not one. And in the docs they discribed it, like a one sql-query, but they have choice_set.all(). How its possible to do with my model? I need all information in the template (html), can you give me some example, how it works?
Thanks.
Related managers' names are automatically generated from model names. You have car.carpictures_set and car.caroptions (this isn't a "set" because it's a one-to-one relationship).
You can define your own related names:
class Car(models.Model):
...
class CarOptions(models.Model):
car = models.OneToOneField(Car, primary_key=True, related_name='options')
class CarPictures(models.Model):
car = models.ForeignKey(Car, related_name='pictures')
Then you'll have car.options and car.pictures.
Related objects reference
Let's say this is your view
cars = Car.objects.filter()
car_options = CarOptions.objects.filter()
car_pictures = CarPictures.objects.filter()
Here how it relates in html
{% for car in cars %}
{% for option in car.car_options %}
{% endfor %}
{% for picture in car.car_pictures %}
{% endfor %}
{% endfor %}

Django three way join using Foreign Keys

I recently started evaluating Django for migrating our archaic web application written 10 years ago. I have been reading up Django documentation for the last few days, but haven't been able to figure out the best way to achieve a multi table database join in my case:
Model:
class Product(models.Model):
productid = models.IntegerField(primary_key=True, db_column='ProductId')
productname = models.CharField(max_length=120, db_column='ProductName')
class Testcases(models.Model):
testcaseid = models.IntegerField(primary_key=True, db_column='TestCaseId')
testcasename = models.CharField(max_length=240, db_column='TestCaseName')
class Testmatrix(models.Model):
testmatrixid = models.IntegerField(primary_key=True, db_column='TestMatrixId')
productid = models.ForeignKey(Product, db_column='ProductId')
testcaseid = models.ForeignKey(Testcases, db_column='TestCaseId')
class Status(models.Model):
testmatrixid = models.ForeignKey(Testmatrix, db_column='TestMatrixId')
title = models.CharField(max_length=240, db_column='Title', blank=True)
(Note that model was generated by inspectdb and I'd prefer not to modify it at this point in time)
View:
from django.shortcuts import render_to_response
from mysite.testmatrix.models import Product, Testcases, Testmatrix, Status
def get_products(request):
tm = list(Testmatrix.objects.filter(productid='abc'))
return render_to_response('products.html', {'tm': tm})
template is designed to be minimal at this point to help focus on the real issue in (views/model).
Template: (products.html)
{% extends "main.html" %}
{% block body %}
<table>
{% for tm in tm %}
<tr>
<td>{{ tm.testmatrixid }}</td>
<td>{{ tm.testcaseid.testcasename }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
Problem:
Although I'm able to join Testmatrix and Testcase models, I am unable to generate an equivalent queryset by joining all of TestMatrix, TestCase, Status records on say productid='abc'
I tried the following:
1) Use select_related between Testmatrix and Testcases and Product tables and was able to access attributes across all three models (testmatrixid, productid, productname, testcaseid, testcasename). However I'm not sure how to extend this auto foreign key referencing to Status model. This would have been easier if all Foreign Keys were defined within Testmatrix itself. But Status has a Foreign Key to TestMatrix.
2) I tried using something like: entries = Status.objects.filter(testmatrixid__productid=pid). This again gave me a queryset as a result of joining Testmatrix and Status, but not Testcases.
Pardon any blaring mistakes or bloopers. This is my very first post!
So you need to access a related_object. It is very simple.
First, add related_name here:
class Status(models.Model):
testmatrixid = models.ForeignKey(Testmatrix, db_column='TestMatrixId', related_name='statuses')
Now you can get all the statuses for desired Testmatrix like
test_matrix.statuses.all()
If you don't want to hit DB, when you access statuses, don't forget to use select_related.
Without any specific error messages, it is hard to diagnose the cause of said errors. However, in your example, in views.get_products: tm = list(Testmatrix.objects.filter(productid='abc')) will not work, because 'abc' is a string and your productid is actually a Product object (not just an integer, even though the field is an integer foreign key to your reference table's pk); you can do tm = list(Testmatrix.objects.filter(productid=Product.objects.get(product_name='abc')), assuming that 'abc' is the product name of the product record. When you set a field to models.ForeignKey(...), you address that reference record as an object, not an id.
Other than that, nothing blaring, your template looks solid and your models look fine to me. I would suggest creating some test cases to see where the errors lie: Django Testing; Also, this is also a great tutorial to understand TDD and unit testing with Django. Using unittests, you can verify every step of your application and make future updates with assurance.

Django left join m2m field

Here's my Model:
class User(models.Model):
pass
class Item(models.Model):
pass
class ItemVote(models.Model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
vote = models.BooleanField()
I want to retrieve a list of Items, and I want to know if the current user has voted for each Item. How do I alter my query object so that it will generate sql similar to:
SELECT ...
FROM items
LEFT OUTER JOIN item_votes ON (item_votes.user_id = ? AND
item_votes.item_id = items.id)
You cannot in plain django queries. This is a many to many relation ship, you can even specify it more clearly by doing something like this:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
In a Many To Many relationship, we can speak of related sets (because there are multiple objects). While django can filter on related sets, something like:
Item.objects.filter(votes=request.user)
this will return you all Items that a user has voted for. However when you will list the .votes attribute in those objects, you will get all users that have voted for that item. the filtering is for the original object, never for the related set.
In a purely django way, you can expand my previous Item class to:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
def markVoted(self, user):
self.voted = user in self.votes
And then call this method for every object. This will however create an additional query to the votes set for every object (and no, select_related does not work for many to many relationships).
The only way to solve this is to add some SQL to your queryset using the extra method, like this:
Item.objects.extra('voted' : 'SELECT COUNT(*) FROM app_itemvote WHERE app_itemvote.item_id = app_item.id AND app_itemvote.user_id=%d'%request.user.pk)
Return ItemVote items:
items = ItemVote.objects.filter(user=user)
use in Django template:
{% for i in items %}
item: {{ i.item }}
voted: {{ i.voted }}
{% endfor %}