Django Admin: Integrity error on "save as" with GenericForeignKey - django

What I have:
a) A simple class to store tags:
class Tag(models.Model):
"""
Defines a tag for any model object.
"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
type = models.ForeignKey(TagType, related_name='+', verbose_name="Typ", db_column='Typ')
value = models.CharField("Wert", max_length=80, db_column='Wert')
b) A mixin that adds the reverse direction to any class:
class TaggedMixin(models.Model):
""" Mixed into a model class, provides it with the ability to receive tags
(i.e. Tag objects).
"""
_tags = GenericRelation(Tag)
c) And some class, call it Data, that inherits from the Mixin. In the admin, this class uses a GenericTabularInline for Tag to display the Data's tags. It also sets "save_as" to True in order to get the "Save As" button on its admin page.
What happens:
When I click "Save As" for a Data object that has at least one tag, I get:
Integrity Error (1048, "Column 'content_type_id' cannot be null")
To me it seems like Django Admin does not handle GenericForeignKeys properly when doing the "Save As". Is there a way to get it running? A way to work around the problem? Or could I be doing something wrong here?
Perhaps I should add, I am using Django 1.7.7 here.
EDIT: We have upgraded to 1.8.2 and the problem still persists. Any ideas?

Related

Displaying fields not intended to be edited in ModelAdmin

I have a custom contact form for which I create a sent_time field using auto_now_add to save the time when the user had sent the message.
I am able to list all the information on the listing view of the admin panel however when I try to enter a specific message I hit the following error:
'sent_time' cannot be specified for GeneralContact model form as it is a non-editable field
My attempt to make the fields readonly in the ModelAdmin results in the same error
class GeneralContactAdmin(ModelAdmin):
"""
Admin model for general correspondence via
the main contact form on the information page
"""
model = GeneralContact
list_display = GeneralContact.__all__
search_fields = GeneralContact.__all__
readonly_fields = GeneralContact.__all__
ordering = ('-sent_time',)
list_filter = ('sent_time', 'has_response')
Surely it is possible to be displayed only, perhaps I've done something incorrectly in my models?
Here is the base model I use for the contact model
class ContactFormBase(models.Model):
__all__ = (
'sent_time', 'sender_name', 'sender_email',
'sender_message', 'has_response', 'responded_on'
)
sent_time = models.DateTimeField(auto_now_add=True)
sender_name = models.CharField()
sender_email = models.EmailField()
sender_message = models.TextField()
has_response = models.BooleanField(
default=False,
help_text='Select whether this message has been replied to by an admin.',
)
responded_on = models.DateTimeField(blank=True, null=True)
panels = [
FieldRowPanel([
FieldPanel('sender_name'),
FieldPanel('sender_email'),
]),
FieldPanel('sent_time'),
FieldPanel('sender_message'),
FieldRowPanel([
FieldPanel('has_response'),
FieldPanel('responded_on'),
])
]
class Meta:
abstract = True
ordering = ['-sent_time',]
The actual class being used is rather plain, perhaps something needs to be done here to allow display of readonly fields?
class GeneralContact(ContactFormBase, models.Model):
panels = ContactFormBase.panels
class Meta:
verbose_name = 'General Contact Entry'
verbose_name_plural = 'General Contact Entries'
In the list view all the information is able to be displayed. In the editing view, ideally there would be all of the information about the message and sender as readonly fields and an option for the admin to change the has_response value based on whether someone has responded or not.
In what way could I achieve this?
update
After seeing this Q&A I have changed the auto_now_add to use django.utils.timezone.now as the default on the sent_time attribute and life seems better, the error from the start of the question is gone and the edit view loads up entirely. However, now all the fields are editable which is not desirable.
Looking into the ModelAdmin class provided by Wagtail it appears that readonly_fields isn't available and perhaps only a feature of the django admin class of the same name. So I'm not sure what to do here. Wagtails HelpPanel type of output is what I'm looking for, and I had an idea to use that to display the data but I'm not sure what that looks like or even how it'd be done as I'm just learning django and wagtail.
update 2
Attempted to use HelpPanel instead of FieldPanel in order to try display the values but seems as if the HelpPanel doesn't retrieve the value of the attributes. Checking through these docs I see the mention of things like djangos readonly_field is not included which confirms why one of my former attempts didn't work but I did find mention of inspect_view_enabled which displays the values in a read only fashion and after trying it out it looks very much how I was trying to get it, alas, nothing there is editable which makes sense but I am getting closer.
I am wondering if a good solution would be to override the view or template used for GeneralContactAdmin but unsure if that's the right way to go about it just to output some text for one class.
A simpler solution is to keep the inspect view and only add the has_response to the edit view, but two views, one of which would only be a checkbox is not a nice for UX.
Surely there is a better way to solve this?

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.

django admin many-to-many intermediary models using through= and filter_horizontal

This is how my models look:
class QuestionTagM2M(models.Model):
tag = models.ForeignKey('Tag')
question = models.ForeignKey('Question')
date_added = models.DateTimeField(auto_now_add=True)
class Tag(models.Model):
description = models.CharField(max_length=100, unique=True)
class Question(models.Model):
tags = models.ManyToManyField(Tag, through=QuestionTagM2M, related_name='questions')
All I really wanted to do was add a timestamp when a given manytomany relationship was created. It makes sense, but it also adds a bit of complexity. Apart from removing the .add() functionality [despite the fact that the only field I'm really adding is auto-created so it technically shouldn't interfere with this anymore]. But I can live with that, as I don't mind doing the extra QuestionTagM2M.objects.create(question=,tag=) instead if it means gaining the additional timestamp functionality.
My issue is I really would love to be able to preserve my filter_horizontal javascript widget in the admin. I know the docs say I can use an inline instead, but this is just too unwieldy because there are no additional fields that would actually be in the inline apart from the foreign key to the Tag anyway.
Also, in the larger scheme of my database schema, my Question objects are already displayed as an inline on my admin page, and since Django doesn't support nested inlines in the admin [yet], I have no way of selecting tags for a given question.
Is there any way to override formfield_for_manytomany(self, db_field, request=None, **kwargs) or something similar to allow for my usage of the nifty filter_horizontal widget and the auto creation of the date_added column to the database?
This seems like something that django should be able to do natively as long as you specify that all columns in the intermediate are automatically created (other than the foreign keys) perhaps with auto_created=True? or something of the like
There are ways to do this
As provided by #obsoleter in the comment below : set QuestionTagM2M._meta.auto_created = True and deal w/ syncdb matters.
Dynamically add date_added field to the M2M model of Question model in models.py
class Question(models.Model):
# use auto-created M2M model
tags = models.ManyToMany(Tag, related_name='questions')
# add date_added field to the M2M model
models.DateTimeField(auto_now_add=True).contribute_to_class(
Question.tags.through, 'date_added')
Then you could use it in admin as normal ManyToManyField.
In Python shell, use Question.tags.through to refer the M2M model.
Note, If you don't use South, then syncdb is enough; If you do, South does not like
this way and will not freeze date_added field, you need to manually write migration to add/remove the corresponding column.
Customize ModelAdmin:
Don't define fields inside customized ModelAdmin, only define filter_horizontal. This will bypass the field validation mentioned in Irfan's answer.
Customize formfield_for_dbfield() or formfield_for_manytomany() to make Django admin to use widgets.FilteredSelectMultiple for the tags field.
Customize save_related() method inside your ModelAdmin class, like
def save_related(self, request, form, *args, **kwargs):
tags = form.cleaned_data.pop('tags', ())
question = form.instance
for tag in tags:
QuestionTagM2M.objects.create(tag=tag, question=question)
super(QuestionAdmin, self).save_related(request, form, *args, **kwargs)
Also, you could patch __set__() of the ReverseManyRelatedObjectsDescriptor field descriptor of ManyToManyField for date_added to save M2M instance w/o raise exception.
From https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models
When you specify an intermediary model using the through argument to a ManyToManyField, the admin will not display a widget by default. This is because each instance of that intermediary model requires more information than could be displayed in a single widget, and the layout required for multiple widgets will vary depending on the intermediate model.
However, you can try including the tags field explicitly by using fields = ('tags',) in admin. This will cause this validation exception
'QuestionAdmin.fields' can't include the ManyToManyField field 'tags' because 'tags' manually specifies a 'through' model.
This validation is implemented in https://github.com/django/django/blob/master/django/contrib/admin/validation.py#L256
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.%s' "
"can't include the ManyToManyField field '%s' because "
"'%s' manually specifies a 'through' model." % (
cls.__name__, label, field, field))
I don't think that you can bypass this validation unless you implement your own custom field to be used as ManyToManyField.
The docs may have changed since the previous answers were posted. I took a look at the django docs link that #Irfan mentioned and it seems to be a more straight forward then it used to be.
Add an inline class to your admin.py and set the model to your M2M model
class QuestionTagM2MInline(admin.TabularInline):
model = QuestionTagM2M
extra = 1
set inlines in your admin class to contain the Inline you just defined
class QuestionAdmin(admin.ModelAdmin):
#...other stuff here
inlines = (QuestionTagM2MInline,)
Don't forget to register this admin class
admin.site.register(Question, QuestionAdmin)
After doing the above when I click on a question I have the form to do all the normal edits on it and below that are a list of the elements in my m2m relationship where I can add entries or edit existing ones.

Django admin raw_id_fields table display

Django raw_id_fields don't display tables the way I expect, am I doing something wrong? I'm trying to use a raw_id_fields widget to edit a ForeignKey field as follows:
#models.py
class OrderLine(models.Model):
product = ForeignKey('SternProduct')
class SternProduct(models.Model):
def __unicode__(self): return self.product_num
product_num = models.CharField(max_length=255)
#admin.py
#import and register stuff
class OrderLineAdmin(admin.ModelAdmin):
raw_id_fields=('product')
I get the little textbox and magnifier widget as expected, but clicking the magnifier gives me this:
flickr.com/photos/28928816#N00/5244376512/sizes/o/in/photostream/
(sorry, can't post more than one hyperlink apparently)
I thought I would get something closer to the changelist page c/w columns, filters and search fields. In fact, that's apparently what others get.
Any thoughts about how to enable the more featureful widget?
Ah, OK, this should have been obvious, but it isn't explained in the Django docs. The list that appears in the raw_id_fields popup uses the same options as the admin object for the referenced model. So in my example, to get a nice looking popup I needed to create a SternProductAdmin object as follows:
class SternProductAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'drawing_number', 'drawing_revision',)
list_filter = ('drawing_number',)
search_fields = ('drawing_number',)
actions = None
Hopefully this will help others in the future.

widgets in django admin

I need a widget which can make a foreignkey readonly and also it should display the value related to that field not the id
suppose
Class A(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=200)
def __unicode__(self):
return self.name
Class B(models.Model):
id=models.AutoField(primary_key=True)
name=models.ForeignKey(A)
description=models.CharField(max_length=200)
now when i make 'name' of class B as readonly then in admin it only displays the id corresponding value of that name in Class A.Is there any widget that can make the field as readonly and also display the value not id
As a workaround you can:
1) Add the field name to raw_id_fields attribute of ModelAdmin and then
2) Disable id input box using javascript (leaving intact the value label).
It will do what you're asking about except for security issue (if someone imitates disabled/deleted input box). That can additionally be dealt with for example in clean_name function of a class inherited from ModelForm.
What if i display the value in the help_text.Means I m showing the value in help_text as well as Id
This can be achievd simply
def get_form(self, request, obj=None):
form = super(BAdmin,self).get_form(request, obj)
link = obj.id
pl=A.objects.get(id=obj.name_id)
help_text1 = "%s"%(pl.name)
form.base_fields['name'].help_text = help_text1
return form
The third workaround is to use Django trunk which adds readonly_fields property to ModelAdmin.
Other alternative is to patch your current version of django with this patch: http://code.djangoproject.com/ticket/342
EDIT: I am using django r12204, because later revisions break django-cms application, which is vital for me. I thought that later revisions of django had this, but I had to patch my django installation to show foreign key values not id's. But it seems that this behaviour still persists in django trunk, so here is the patch: http://dpaste.com/hold/147814/