ForeignKey field will not appear in Django admin site - django

A foreign key on a model is not appearing in the Django admin site. This is irrespective of whether the field is explicitly specified in a ModelAdmin instance (fields = ('title', 'field-that-does-not-show-up')) or not.
I realize there are many variables that could be causing this behavior.
class AdvertiserAdmin(admin.ModelAdmin):
search_fields = ['company_name', 'website']
list_display = ['company_name', 'website', 'user']
class AdBaseAdmin(admin.ModelAdmin):
list_display = ['title', 'url', 'advertiser', 'since', 'updated', 'enabled']
list_filter = ['updated', 'enabled', 'since', 'updated', 'zone']
search_fields = ['title', 'url']
The problem is the advertiser foreign key is not showing up in the admin for AdBase
class Advertiser(models.Model):
""" A Model for our Advertiser
"""
company_name = models.CharField(max_length=255)
website = models.URLField(verify_exists=True)
user = models.ForeignKey(User)
def __unicode__(self):
return "%s" % self.company_name
def get_website_url(self):
return "%s" % self.website
class AdBase(models.Model):
"""
This is our base model, from which all ads will inherit.
The manager methods for this model will determine which ads to
display return etc.
"""
title = models.CharField(max_length=255)
url = models.URLField(verify_exists=True)
enabled = models.BooleanField(default=False)
since = models.DateTimeField(default=datetime.now)
expires_on=models.DateTimeField(_('Expires on'), blank=True, null=True)
updated = models.DateTimeField(editable=False)
# Relations
advertiser = models.ForeignKey(Advertiser)
category = models.ForeignKey(AdCategory)
zone = models.ForeignKey(AdZone)
# Our Custom Manager
objects = AdManager()
def __unicode__(self):
return "%s" % self.title
#models.permalink
def get_absolute_url(self):
return ('adzone_ad_view', [self.id])
def save(self, *args, **kwargs):
self.updated = datetime.now()
super(AdBase, self).save(*args, **kwargs)
def impressions(self, start=None, end=None):
if start is not None:
start_q=models.Q(impression_date__gte=start)
else:
start_q=models.Q()
if end is not None:
end_q=models.Q(impression_date__lte=end)
else:
end_q=models.Q()
return self.adimpression_set.filter(start_q & end_q).count()
def clicks(self, start=None, end=None):
if start is not None:
start_q=models.Q(click_date__gte=start)
else:
start_q=models.Q()
if end is not None:
end_q=models.Q(click_date__lte=end)
else:
end_q=models.Q()
return self.adclick_set.filter(start_q & end_q).count()
class BannerAd(AdBase):
""" A standard banner Ad """
content = models.ImageField(upload_to="adzone/bannerads/")
The mystery deepens. I just tried to create a ModelForm object for both AdBase and BannerAd, and both generated fields for the advertiser. Some crazy admin things going on here...

I believe I've just run into exactly the same problem, but was able to debug it thanks to the help of persistent co-workers. :)
In short, if you look in the raw HTML source you'll find the field was always there - it's just that:
Django tries to be clever and put the form field inside a div with CSS class="form-row $FIELD_NAME",
The field's name was "advertiser", so the CSS class was "form-row advertiser",
...Adblock Plus.
Adblock Plus will hide anything with the CSS class "advertiser", along with a hell of a lot of other CSS classes.
I consider this a bug in Django.

maybe it is an encode error. I had the same problem, but when i added # -- coding: UTF-8 -- in the models.py, all fine.

Another very dumb cause of the same problem:
If there is only one instance of the related model, then the filter simply won't show. There is a has_output() method in RelatedFieldListFilter class that returns False in this case.

It's a strange problem for sure. On the AdBase model if you change
advertiser = models.ForeignKey(Advertiser)
to
adver = models.ForeignKey(Advertiser)
then I believe it'll show up.

Powellc, do you have the models registered with their respective ModelAdmin class?
admin.site.register(Advertiser, AdvertiserAdmin) after the ModelAdmin definitions.

You are talking about the list_display option, right?
Is the unicode-method for your related model set?
If the field is a ForeignKey, Django
will display the unicode() of the
related object
Also check this thread for some hints: Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?

Try disabling your ad blocker. No, this is not a joke. I just ran into this exact problem.

We just ran into this problem.
It seems that if you call you field advertiser the in the admin the gets given an 'advertiser' class.
Then is then hidden by standard ad blocking plugins. If you view source your field will be there.

Related

Django Admin overwrite the __str__ method in an autocomplete_field

I want to overwrite the __str__ method in Django admin when using the autocomplete_fields = () but the returned values are using __str__.
I have a form something like
class MyAdminForm(forms.ModelForm):
placement = forms.Select(
choices = Organisation.objects.active(),
)
class Meta:
model = Lead
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['placement'].label_from_instance = lambda obj: f'{str(obj)} {obj.post_code}'
This will provide back a Select with the organisation name and post code in the dropdown fields. But there are some 80k choices so I need to using autocomplete. Within within admin.py I have
class LeadAdmin(admin.ModelAdmin):
form = LeadAdminForm
autocomplete_fields = ('placement',)
As soon as I add the autocomplete_fields I lose my postcode and it reverts to just showing the __str__
Hoa can I used autocomplete_fields and overwrite the __str__ method?
This question is answered through Benbb96 comment above which I've copied here so I can close it
So maybe this answer can help you :
stackoverflow.com/a/56865950/8439435 – Benbb96

Add method doesnt work when trying to establish m2m relationships using post_save in Django

My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. I'm doing this the following way.
def tag_content(obj):
for tag in Tag.objects.all():
print tag
obj.tags.add(tag)
obj.is_tagged = True
obj.save()
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
#receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'])
The tag_content function runs, however, the m2m relationships are not established. Im using Django 1.9.8 btw. This makes no sense. What am I missing? Moreover, if I do something like tag_content(content_instance) in shell, then the tags are set, so the function is ok. I guess the problem is in the receiver. Any help?
Edit
My question has nothing to do with m2m_changed, as I have said, creating a Content object in shell works perfectly. Therefore, the problem lies in the admin panel's setup.
Ok so I solved the problem. Basically, this has something to do with how Django handles its form in the admin panel. When trying to add the Contents from admin, I kept the tags field empty, thinking the tag_content function would handle it. However, that is exactly where the problem was, as creating a Content from shell tagged it just fine. In other words, changing the admin panel to something like this solved my problem :
from django.contrib import admin
from myapp.models import *
from django import forms
class ContentCreationForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title',)
class ContentChangeForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title', 'is_tagged', 'tags')
class ContentAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj is None:
return ContentCreationForm
else:
return ContentChangeForm
admin.site.register(Tag)
admin.site.register(Content, ContentAdmin)
When trying to create a new Content, only the 'title' field is presented. This solves the problem.

Django admin: make field editable in add but not edit

I've got a model similar to this:
class Product(models.Model):
third_party_id = models.CharField(max_length=64, blank=False, unique=True)
that uses the Django default primary key. I want users to be able to add products by setting the third_party_id on the add page, but I don't want that field editable in the edit page to avoid corrupting the third_party_id. In the Django docs, the same settings appear to be used for add and edit. Is this possible?
Do not set self.readonly_fields to avoid thread issues. Instead override get_readonly_fields method:
def get_readonly_fields(self, request, obj=None):
if obj: # obj is not None, so this is an edit
return ['third_party_id',] # Return a list or tuple of readonly fields' names
else: # This is an addition
return []
The above is helpful (shanyu's answer using get_readonly_fields), however it does not work properly if used in "StackedInline". The result is two copies of whatever field is marked readonly, and it is not editable in the "add" instance. See this bug: https://code.djangoproject.com/ticket/15602
Hope this saves someone some searching!
I am not sure if this is the best way, but you could define your own form for the admin. And custom validate your third_party_id, rejecting if it is already set:
Admin.py
class ProductAdminForm(forms.ModelForm):
class Meta:
model = Product
def clean_third_party_id(self):
cleaned_data = self.cleaned_data
third_party_id = cleaned_data['third_party_id']
id = cleaned_data['id']
obj = Product.objects.get(id=id)
if obj.third_party_id != third_party_id:
raise ValidationError("You cannot edit third_party_id, it must stay as %s" % obj.third_party_id)
return third_party_id
class ProductAdmin(admin.Admin):
form = [ProductAdminForm,]

Django admin TabularInline - is there a good way of adding a custom html column?

I've got a model (Entry) which contains a simple property:
#property
def image(self):
return str(self.id)+"_"+self.round.season.name+"_"+self.round.theme+"_"+self.person.name
I use this to build the name of a particular image file on disk. So I know that there's going to be an image at /path/to/images/(model.image()).jpg
I can display the raw image property itself within the TabularInline layout on an admin page by adding it to the readonly_fields collection, but how would I go about getting a column which had custom html wrapped around the model property?
e.g.
<img src="/images/{{model.image}}.jpg" />
What you can do is create a method in your TabularInline subclass that returns the HTML you want, then use that method's name in place of image in ImageInline.fields:
from django.utils.safestring import mark_safe
class ImageInline(admin.TabularInline):
...
fields = (..., 'render_image')
readonly_fields = (..., 'render_image')
def render_image(self, obj):
return mark_safe("""<img src="/images/%s.jpg" />""" % obj.image)
According to current Django 1.2+ I got errors "Form does not have such field as render_image". Solution is simple put the render_image function into model.Admin not in your inline form, second thing is fields and readonly_fields settings in your Inline form... So here You have what I've ended up with:
class OfferPropertyInline(admin.TabularInline):
model = OfferProperty
fields=('property_value',)
readonly_fields = ('property_value',)
class OfferAdmin(admin.ModelAdmin):
inlines = [
OfferPropertyInline
]
def property_value(self,obj):
return obj.get_value()
admin.site.register(Offer, OfferAdmin)
Lechup's answer does not work for me, I am using Django 1.11.7. I found this way to work around.
Let say I have 2 tables: Campaign and Article, one campaign has many articles. I want to show the articles when browsing a specific campaign.
Table Article has a column named score, which is a float. I want to round it up to 2 decimal places when viewing in Django admin.
This example shows how you can make a custom column for TabularInline in Django admin.
class Article(models.Model):
title = models.TextField(null=False)
url = models.TextField()
score = models.FloatField(null=True)
def __str__(self):
return self.title
def display_score(self):
if self.score:
return round(self.score, 2)
return self.score
display_score.short_description = 'Score'
class ArticleInline(admin.TabularInline):
model = Article
readonly_fields = ('title', 'url', 'display_score')
fields = ('title', 'url', 'display_score')
class CampaignAdmin(admin.ModelAdmin):
inlines = [ArticleInline]
admin.site.register(Campaign, CampaignAdmin)
#lechup correct except you need:
readonly_fields = ('mycustomfield',)
defined in the Inline for later versions of django (+1.4)

Django admin: How to display a field that is marked as editable=False' in the model?

Even though a field is marked as 'editable=False' in the model, I would like the admin page to display it. Currently it hides the field altogether.. How can this be achieved ?
Use Readonly Fields. Like so (for django >= 1.2):
class MyModelAdmin(admin.ModelAdmin):
readonly_fields=('first',)
Update
This solution is useful if you want to keep the field editable in Admin but non-editable everywhere else. If you want to keep the field non-editable throughout then #Till Backhaus' answer is the better option.
Original Answer
One way to do this would be to use a custom ModelForm in admin. This form can override the required field to make it editable. Thereby you retain editable=False everywhere else but Admin. For e.g. (tested with Django 1.2.3)
# models.py
class FooModel(models.Model):
first = models.CharField(max_length = 255, editable = False)
second = models.CharField(max_length = 255)
def __unicode__(self):
return "{0} {1}".format(self.first, self.second)
# admin.py
class CustomFooForm(forms.ModelForm):
first = forms.CharField()
class Meta:
model = FooModel
fields = ('second',)
class FooAdmin(admin.ModelAdmin):
form = CustomFooForm
admin.site.register(FooModel, FooAdmin)
Add the fields you want to display on your admin page.
Then add the fields you want to be read-only.
Your read-only fields must be in fields as well.
class MyModelAdmin(admin.ModelAdmin):
fields = ['title', 'author', 'published_date', 'updated_date', 'created_date']
readonly_fields = ('updated_date', 'created_date')
You could also set the readonly fields as editable=False in the model (django doc reference for editable here). And then in the Admin overriding the get_readonly_fields method.
# models.py
class MyModel(models.Model):
first = models.CharField(max_length=255, editable=False)
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields if not f.editable]
With the above solution I was able to display hidden fields for several objects but got an exception when trying to add a new object.
So I enhanced it like follows:
class HiddenFieldsAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
try:
return [f.name for f in obj._meta.fields if not f.editable]
except:
# if a new object is to be created the try clause will fail due to missing _meta.fields
return ""
And in the corresponding admin.py file I just had to import the new class and add it whenever registering a new model class
from django.contrib import admin
from .models import Example, HiddenFieldsAdmin
admin.site.register(Example, HiddenFieldsAdmin)
Now I can use it on every class with non-editable fields and so far I saw no unwanted side effects.
You can try this
#admin.register(AgentLinks)
class AgentLinksAdmin(admin.ModelAdmin):
readonly_fields = ('link', )