Retrieve field values to Django Admin using many to many relationships - django

I have a simple task: I need to expose player name related to Game in game list (Django Admin). Game object has ManyToMany relationship with Player object via 'players' attribute. The problem is that now I have empty 'players_list' field (empty list means I don't know what to do and just leave it here[enter image description here][1]), though I tried Player.objects.all() and obviously got all players even those who are not bound to particular game.
I feel it has simple solution but my brain refuses to work after 55 opened tabs.
Thanks in advance!
This is my models.py
from django.db import model
class Player(models.Model):
name = models.CharField(max_length=54, default="")
email = models.EmailField(max_length=54)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Game(models.Model):
name = models.CharField(max_length=254, default="")
players = models.ManyToManyField(Player, blank=True, related_name='player_games')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
players_list = []
def __str__(self):
return self.name
and admin.py
from django.contrib import admin
from .models import Game, Player
class PlayerInline(admin.TabularInline):
model = Game.players.through
#admin.register(Player)
class Admin(admin.ModelAdmin):
search_fields = ['name', 'email']
list_display = ('name', 'email', 'created_at', 'updated_at')
inlines = [
PlayerInline,
]
#admin.register(Game)
class AdminAdmin(admin.ModelAdmin):
list_display = ('name', 'created_at', 'updated_at', 'players_list')
inlines = [
PlayerInline,
]
exclude = ('players',)
Pic as it looks now
[1]: https://i.stack.imgur.com/KVJ5y.png

The best approach here will be creating custom method in your model instead of single variable. You will not even have to change your list_display:
class Game(models.Model):
...
def players_list(self):
return self.players.all()
Alternatively, if you want only names of players or anything else from, you can change it (or add another method) to something like that:
class Game(models.Model):
...
def players_list(self):
return [player for player in self.players.all()]
# or
return [player.name for player in self.players.all()]
This is called list comprehension, if you are not familiar with it.

Related

Selecting items from a many to many field to link specific items in the list to a model in the admin panel

I am working on a recipe book app, and I currently have my models connected in this way:
class Tool(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=200)
def __str__(self):
return self.name
class Recipe(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=200)
servings = models.IntegerField(default=1, blank=False)
tools = models.ManyToManyField(Tool)
def __str__(self):
return self.name
The admin panel input currently looks like this:
Upon saving the data as shown in the screenshot, I get the following error:
OperationalError at /admin/recipeBook/recipe/add/
no such table: recipeBook_recipe_tools
Here is my admin.py, just incase it's useful:
from django.contrib import admin
from .models import Recipe, Ingredient, Instruction, Tool
# Register your models here.
class IngredientInline(admin.TabularInline):
model = Ingredient
extra = 2
class RecipeAdmin(admin.ModelAdmin):
fieldsets = [
('Information', {'fields': ['name', 'description', 'servings']}),
('Tools', {'fields': ['tools']})
]
inlines = [IngredientInline]
list_display = ('name', 'description', 'servings')
search_fields = ['name']
list_filter = ['servings']
admin.site.register(Recipe, RecipeAdmin)
admin.site.register(Ingredient)
admin.site.register(Instruction)
admin.site.register(Tool)
If you have an error like this.
Make sure you migrate your database after adding to your models.

Django Rest Framework - how to get only one field from related models set

I have following models:
from django.db import models
class City(models.Model):
name = models.CharField(max_length=30)
last_update = models.DateTimeField(null=True)
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
Now using Django Rest Framework, I would like to create serializer that will return City details along with the list of all BusStops in the city - but I want the list to be only strings with BusStop names, like this:
{
"id": 1
"name": "City"
"last_update": "2019-09-19T22:13:54.851363Z"
"bus_stops": [
"stop1",
"stop2",
"stop3"
]
}
What I've tried so far is following serializers:
from rest_framework import serializers
class BusStopSerializer(serializers.ModelSerializer):
class Meta:
model = BusStop
fields = ('name', )
class CityDetailsSerializer(serializers.ModelSerializer):
busstop_set = BusStopSerializer(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'busstop_set')
But this creates list of objects with 'name' in them. So, how can I create a list with only BusStop names (as strings) in it?
Instead of the extra BusStopSerializer you could use a StringRelatedField:
# models.py
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
def __str__(self):
return self.name
# serializers.py
class CityDetailsSerializer(serializers.ModelSerializer):
bus_stops = StringRelatedField(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'bus_stops')
StringRelatedField, as recommended by wfehr, will only work as long as the __str__ method of BusStop only returns the name. An alternative is to use SlugRelatedField which allows you to specify a particular field from the related model, and has no dependency on __str__.
bus_stops = SlugRelatedField(many=True, slug_field='name')

Django - assign m2m relationship in admin

In the app I'm building some users have the role "Coder" and are assigned to "Assignments".
What I can't seem to get working is the process of an Admin assigning Coders to Assignments.
Here is the Model-Code I have so far (probably totally wrong):
class Coder(models.Model):
"""Every user can be a coder. Coders are assigned to Assignments"""
user = models.OneToOneField(User)
class Admin:
list_display = ('',)
search_fields = ('',)
def __unicode__(self):
return u"Coders"
class Assignment(models.Model):
"""(Assignment description)"""
title = models.CharField(blank=True, max_length=100)
start_year = models.IntegerField(blank=True, null=True)
end_year = models.IntegerField(blank=True, null=True)
country = models.IntegerField(blank=True, null=True)
coders = models.ManyToManyField(Coder)
class Admin:
list_display = ('',)
search_fields = ('',)
def __unicode__(self):
return u"Assignment"
And this is the admin-code:
class AssignmentCoderInline(admin.StackedInline):
model = Assignment.coders.through
can_delete = False
verbose_name_plural = 'coder'
class AssignmentAdmin(admin.ModelAdmin):
fieldsets = [
('Assignment', {'fields': ['title', 'start_year', 'end_year']})
]
inlines = (AssignmentCoderInline,)
class CoderInline(admin.StackedInline):
model = Coder
can_delete = False
verbose_name_plural = 'coder'
Now, when i'm in the admin, I want to create an assignment and add coders to it.
Yet all I see when trying to do so is this:
How can I add one coder/user to an assignment, so I can later show him in a view all the assignments he has?
This is probably a really dumb question, but please answer anyways, I greatly appreciate any help :)
If I'm not mistaken, it looks like you want to call a coder to a view, and then show all the assignments for an a user.
First, I might start by assigning a related_name to the coder and assignment models relationships with each other, so you can easily reference them later.
class Assignment(models.Model):
coders = models.ManyToManyField(Coder, related_name='assignments')
class Coder(models.Model):
user = models.OneToOneField(User, related_name="coder")
I'd then reference the user in a template as such using the one to one relationship:
{% for assignment in user.coder.assignments.all %}
Also, looks like the problem is how you've got yout models setup. After reviewing the django.db.models.base for the "models.Model" class and "ModelBase" class, it looks like there is no "Admin" subclass. You'll probably want to remove those to start with.
Next, the __unicode__ field shows the default visible value which represents the object on the screen. In this case, you've forced it to be "Coders". If you had 5 coders to an assignment, you'd see "Coders, Coders, Coders, Coders, Coders" instead of "admin, user1, bob22, harry8, stadm1in", etc. Let's override the unicode to show something more meaningful. Since the coders field only has the user field, let's reference that by self.user.username. We'll change Assignment()'s unicode to self.title as well.
ModelForm doesn't have an 'Admin' subclass either, so let's remove that.
MODELS:
class Coder(models.Model):
"""Every user can be a coder. Coders are assigned to Assignments"""
user = models.OneToOneField(User, related_name='coder')
def __unicode__(self):
return self.user.username
class Assignment(models.Model):
"""(Assignment description)"""
title = models.CharField(blank=True, max_length=100)
start_year = models.IntegerField(blank=True, null=True)
end_year = models.IntegerField(blank=True, null=True)
country = models.IntegerField(blank=True, null=True)
coders = models.ManyToManyField(Coder, related_name='assignments')
def __unicode__(self):
return self.title
ADMIN:
class AssignmentCoderInline(admin.StackedInline):
model = Assignment.coders.through
can_delete = False
# verbose_name_plural = 'coder'
class AssignmentAdmin(admin.ModelAdmin):
fieldsets = [
('Assignment', {'fields': ['title', 'start_year', 'end_year']})
]
inlines = (AssignmentCoderInline,)

Django Admin - Multiple Inlines

Having a bit of trouble using multiple inlines within my admin console over 3 models which im playing around with.
Models:
class carManufacturer(models.Model):
name = models.CharField(max_length=200)
country = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
def __unicode__(self):
return self.name
class vehicleModel(models.Model):
carManufacturer = models.ForeignKey(carManufacturer)
model = models.CharField(max_length=200)
def __unicode__(self):
return self.model
class vehicleCode(models.Model):
vehicleModel = models.ForeignKey(vehicleModel)
variantCode = models.CharField(max_length=200)
variantBadge = models.CharField(max_length=200)
manuStart = models.DateTimeField('Manufacture Start Date')
manuFin = models.DateTimeField('Manufacture End Date')
def __unicode__(self):
return self.variantCode
What I'm looking to do is when I add a car Manufacturer I can add car Models via my inline, and when I am editing models, I can edit vehicle codes/variants via another inline.
I have an admin.py file I am using:
from Cars.models import carManufacturer, vehicleModel, vehicleCode
from django.contrib import admin
class modelInline(admin.TabularInline):
model = vehicleModel
extra = 0
class codeInline(admin.TabularInline):
variantCode = vehicleCode
extra = 0
class CarAdmin(admin.ModelAdmin):
fields = ['name', 'description', 'country']
inlines = [modelInline]
class VehicleModelAdmin(admin.ModelAdmin):
fields = ['carManufacturer','model']
#inlines = [codeInline]
admin.site.register(carManufacturer, CarAdmin)
admin.site.register(vehicleModel, VehicleModelAdmin)
As soon as I uncomment my second inline which uses the same method as the first I get the following error:
'model' is a required attribute of 'VehicleModelAdmin.inlines[0]'.
I am struggling to understand what I am doing wrong, especially since I have got the first inline working, any input would be much appreciated
The codeInline doesn't have model field for any TabularInline you do need model field like one above. It should have something like following
class codeInline(admin.TabularInline):
model = vehicleCode
extra = 0

Show the number of foreign key objects in admin listview

I have a Competition model which has a corresponding CompetitionEntry model. I'd like to show the number of entries for each competition in the admin view.
Here's the model definition:
class Competition(models.Model):
def __unicode__(self):
return self.competition_name
competition_name = models.CharField(max_length=100)
competition_text = models.TextField()
active = models.BooleanField('Is this competition active?', blank=True)
date_posted = models.DateTimeField(auto_now_add=True)
class CompetitionEntry(models.Model):
def __unicode__(self):
return self.competition.competition_name
competition = models.ForeignKey(Competition)
user = models.ForeignKey(User)
date_entered = models.DateTimeField(auto_now_add=True)
is_winner = models.BooleanField('Is this entry the winner?', blank=True)
My Django skills are a little rusty, but there should be a fairly simple way to add this to the admin, right? Any pointers? I can't quite work out how the Competition class can 'talk' to the CompetitionEntry class since the relationship is defined inside CompetitionEntry, but I want to show the entries inside Competition.
You can refer to python functions in a ModelAdmin by adding it to the fieldsets or list_display and readonly_fields attributes.
You can 'talk' to a reverse relationship via the reverse related managers dynamically added to each class that a foreign key points to which is, by default, lowercasemodelname_set and behaves exactly like your default objects manager.
class MyAdmin(admin.ModelAdmin):
list_display = ('_competition_count',)
readonly_fields = ('_competition_count',)
fieldsets = (
(None, {'fields': (
'_competition_count',
)})
)
def _competition_count(self, obj):
return obj.competitionentry_set.count()
_competition_count.short_description = "Competition Count"