Django admin: Inline of a Many2Many model with 2 foreign keys - django

after wracking my brain for days, I just hope someone can point me to the right approach.
I have 4 Models: Page, Element, Style and Post.
Here is my simplyfied models.py/admin.py excerpt: http://pastebin.com/uSHrG0p2
In 2 sentences:
A Element references 1 Style and 1 Post (2 FKs).
A Page can reference many Elements, Elements can be referenced by many pages (M2M).
On the admin site for Page instances I included the M2M relation as 'inline'. So that I have multiple rows to select Element-instances.
One row looking like: [My Post A with My Style X][V]
What I want is to replace that one dropdown with 2 dropdowns. One with all instances of Post and one with all instances of Style (creating Element instances in-place). So that one row would look similar to the Element admin site: [My Post A][V] [My Style X][V]
Sounds easy, but I'm just completely lost after reading and experimenting for 2 days with ModelForms, ModelAdmins, Formsets, ... .
Can I do that without custom views/forms within the Django admin functionality?
One of my approaches was to access the Post/Style instances from a PageAdminForm like this, trying to create a form widget manually from it... but failed to do so:
p = Page.objects.get(pk=1)
f = PageAdminForm(instance=p)
f.base_fields['elements'].choices.queryset[0].post
Any advice or hint which way I need to go?
Thank you for your time!

I got exactly what I wanted after removing the M2M field and linking Elements to a Page with a 3rd ForeignKey in Element:
class Element(models.Model):
page = models.ForeignKey(Page)
post = models.ForeignKey(Post)
style = models.ForeignKey(Style)
Actually a non-M2M link makes more sense for my application after all.
Memo to self: Rethink model relations before trying to outsmart Django :-(

Related

Best way to manage several DB Columns and 1 widget in Django

I have a models with several db fields (field1, field2)
I want to create an unique widget to fill value for field1,field2
Possible solution ?
set my widget on field1, and set HiddenInput for field2. But I don't like so much this solution. Is that correct solution at least ?
Is there some other solutions ? like create 1 FormField that is mapped to 2 DB Fields (Not easy to do that in Django Admin)
I checked several solution for similar needs LatLonField, but in the examples I found, lat and lot are stored in a commaseparatedfield which is not acceptable for me.

Managing list of lists in django admin

I am writing a django application, that stores and displays working hours of employees.
The problem is, that for example pediatrists have 2 types of working hours - separate for sick children and healthy ones.
So I thought, it would be cool, to use HTML table to display the hours for each employee. My idea was to have a "ListField" representing each row of table, with ForeignKey to employee. That way, admin could create lists like:
['', 'Sick Children', 'Healthy Children'],
['Monday', '8-12', '12-14'],
['Friday', '12-15']
And it would appear on website, as an HTML table, that would look pretty nice.
The thing is, I would love it to look easy and intuitive for an admin of website. So I would love to keep the table rows as inline of employee in admin panel.
So, I have created models:
class TableRow(models.Model):
employee = models.ForeignKey(Employee)
class TableCell(models.Model):
content = models.CharField(max_length=20)
row = models.ForeignKey(TableRow)
And tried stuff like:
class TableCellInline(admin.TabularInline):
model = TableCell
class TableRowInline(admin.TabularInline):
model = TableRow
class EmployeeAdmin(admin.ModelAdmin):
inlines = [TableRowInline]
admin.site.register(Employee, EmployeeAdmin)
admin.site.register(TableRow, TableRowAdmin)
Which doesn't work (as I expected, but didn't hurt to try). Admin panel shows an option to add table row, when adding/editing employee, but doesn't show any option to add any cell to the row.
Is there any way to allow adding the rows while editing/adding employee? Or maybe a totally different way to solve the problem?
What you're trying to do is commonly referred to as nested inlines. Unfortunately, I'm afraid this is still not supported by the admin. See the following resources for more information.
Nested inlines in the Django admin?
#9025 assigned New feature: Nested Inline Support in Admin

Get most commented posts in django with django comments

I'm trying to get the ten most commented posts in my django app, but I'm unable to do it because I can't think a proper way.
I'm currently using the django comments framework, and I've seen a possibility of doing this with aggregate or annotate , but I can figure out how.
The thing would be:
Get all the posts
Calculate the number of comments per post (I have a comment_count method for that)
Order the posts from most commented to less
Get the first 10 (for example)
Is there any "simple" or "pythonic" way to do this? I'm a bit lost since the comments framework is only accesible via template tags, and not directly from the code (unless you want to modify it)
Any help is appreciated
You're right that you need to use the annotation and aggregation features. What you need to do is group by and get a count of the object_pk of the Comment model:
from django.contrib.comments.models import Comment
from django.db.models import Count
o_list = Comment.objects.values('object_pk').annotate(ocount=Count('object_pk'))
This will assign something like the following to o_list:
[{'object_pk': '123', 'ocount': 56},
{'object_pk': '321', 'ocount': 47},
...etc...]
You could then sort the list and slice the top 10:
top_ten_objects = sorted(o_list, key=lambda k: k['ocount'])[:10]
You can then use the values in object_pk to retrieve the objects that the comments are attached to.
Annotate is going to be the preferred way, partially because it will reduce db queries and it's basically a one-liner. While your theoretical loop would work, I bet your comment_count method relies on querying comments for a given post, which would be 1 query per post that you loop over- nasty!
posts_by_score = Comment.objects.filter(is_public=True).values('object_pk').annotate(
score=Count('id')).order_by('-score')
post_ids = [int(obj['object_pk']) for obj in posts_by_score]
top_posts = Post.objects.in_bulk(post_ids)
This code is shameless adapted from Django-Blog-Zinnia (no affiliation)

Multi-level hierarchy dropdown in django?

I'm looking to categorize my entries, the catch is there are multiple levels of categories I want. An example:
css
layout
floats
specificity, selectors
html
html 5
In this example, css and html are parent categories, css has 2 children and layout has a child of floats.
I suppose the schema I would need would be
class Category:
name = models.TextField()
parentId = models.IntegerField(blank=True)
What I'm clueless on is, how would I be able to make a multi-level dropdown in my admin so that when I post entries I could select a category easily?
So to reiterate, how would I be able to generate a multi-level nested dropdown menu so that when I enter stuff in my Entry model, I can select one category per entry?
It seems that your problem is slightly different from what you are stating. The issue here is not that much about how to display the hierarchy, which is simple:
def __unicode__(self):
return self.depth * " "
The bummer is how to capture and display the hierarchy / depth. This is a common problem: storing trees in realational databases. As usual, your solution depends on tradeoffs between write / read heavy and how much to normalize. You could, for example, on the 'save' method of the model to recursively get to the root and from there store a 'depth' attribute on the nodes. My suggestion is to use django mptt . It is pretty solid and solves much of the normal hurdles. As a bonus, you get a good api for common tree tasks.

Django Form Models and Editing

Wow, I'm having such a hard time on this. I must be doing something wrong, I'm getting an incredible ammount of queries.
So, my models are the following:
Player
Clan
Match (as in game match)
MatchMap (the maps played of a match)
MatchPlayer (the players of a match)
All of them are related via foreign key, no m2m relationship. A player can be in a clan and a match involves 2 clans. A match can have any ammount of maps and only players from the two clans involved can be in the match (although on the future the players might not be in the same clan they played on that match, so I specify the side they played on the match).
So, I made the submit match, all ok. But to edit this info, it's caos!
For editing MatchPlayers of a match I tried using inlineformset_factory
PlayersFormSet = inlineformset_factory(MatchBetweenClans, MatchPlayer)
playersForms = PlayersFormSet(instance=match)
This already starts bad because for each instance of player on a match, Django hits the database and gets a list of all players. If for example 6 players are on a match, and I have 2 empty forms provived by the inlineformset_factory, I see on the log
SELECT
...
FROM
`accounts_customuser`
8 times.
Now, even if that worked correctly for me, it does not do what I need. When adding players to a match, not all players should be on the list, only those from the 2 specified clans, ideally as the form of checkboxes (each checkbox being a player of a clan). When submitting the match this is easy to do:
clan1PlayerList = CustomUser.objects.filter(clan=clan1Instance)
clan2PlayerList = CustomUser.objects.filter(clan=clan2Instance)
playersClan1 = forms.ModelMultipleChoiceField(queryset=clan1PlayerList, label="Jogadores - "+clan1Instance.tag+"", widget=forms.CheckboxSelectMultiple(attrs={'class':'input_checkbox'}))
playersClan2 = forms.ModelMultipleChoiceField(queryset=clan2PlayerList, label="Jogadores - "+clan2Instance.tag, widget=forms.CheckboxSelectMultiple(attrs={'class':'input_checkbox'}))
Is there anyway I could have this on a formulary to be edited? I can't find a way to send the playerlist of a clan and the currect players of a match to a form.
Thanks
You might want to look at select_related.
Here are the docs.
I had to go read your question again, as the question part of it wasn't clear to me. I thought you had a problem of too many queries hitting the database (which may be a problem too and select related will help there), but your question really is:
How to filter a form field to only allow values based on a different field?
Correct? From your question:
When adding players to a match, not all players should be on the list, only those from the 2 specified clans, ideally as the form of checkboxes (each checkbox being a player of a clan).
In your view, filter the fields on each form by clan. Something like (without your models I can only guess at field names):
form.fields['player'].queryset=form.fields['player'].queryset.filter(clan__in=list_of_selected_clans)
Unrelated suggestion: look at python's string interpolation.
Boa sorte!