Django admin causes high load for one model - django

In my Django admin, when I try to view/edit objects from one particular model class the memory usage and CPU rockets up and I have to restart the server. I can view the list of objects fine, but the problem comes when I click on one of the objects. Other models are fine. Working with the object in code (i.e. creating and displaying) is ok, the problem only arises when I try to view an object with the admin interface. The class isn't even particularly exotic:
class Comment(models.Model):
user = models.ForeignKey(User)
thing = models.ForeignKey(Thing)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField(blank=True, null=True)
approved = models.BooleanField(default=True)
class Meta:
ordering = ['-date']
Any ideas? I'm stumped. The only reason I could think of might be that the thing is quite a large object (a few kb), but as I understand it, it wouldn't get loaded until it was needed (correct?).

It's not really a question of how big the Thing object is, but rather of how many you have in your database. That's because for a ForeignKey, by default Django's admin gives you a drop-down list containing all the existing items. If you've got lots and lots, then Django will load them all in order to populate that list. The same is true here of User.
The best way round this is to add the offending field to the raw_id_fields in your ModelAdmin subclass. That will change the representation to a simple textfield for the id, with a pop-up lookup window.

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?

django remove least recent many to many entry

In my Django app, I want to allow users to see which profiles they view and which profiles view them. In my Profile model I have created 2 fields that accomplish this.
viewed = models.ManyToManyField('self', null=True, blank=True, related_name='viewed_profiles', symmetrical=False)
visitors = models.ManyToManyField('self', null=True, blank=True, related_name='visitors_profiles', symmetrical=False)
I also have the code set up in my views.py file to add profiles to these fields as necessary. However, I would like to only track and display the most recent 25 or so viewed and visitor profiles. Is there a way to query these fields ordered by date added and delete everything past the first 25 results? Is this possible without creating another field to track the order of the profiles viewed?
Take a look at the documentation on Querysets for details of how to do this. You can use order_by to order your objects by date, and use Python's array slicing syntax to limit the number of results.
An example of showing the most recently added items in your view might look something like this:
viewed = Profile.objects.order_by("-date_added")[:25]
This doesn't delete everything after 25 - it just fetches the 25 most recent objects (assuming your Profile model has a field called date_added).
EDIT: Oops, I think I misread your question.
I think what you'd need to do is have an intermediate model - Django allows you to use a third model as an intermediate one between two different models in a many-to-many relationship. Then you could add the time viewed to that model and store it that way. There's a good example in the documentation.
I wouldn't really bother deleting the old ones unless database space was likely to be an issue, but if you need to for any reason, I guess you could set up a signal that was triggered by a new view being created and have that call a function that deletes all but the 25 most recent.
Django doesn't track the date added for a ManyToMany relationship, so it's not possible to do this reliably without adding a field. To achieve this you'll need to do is add a date field on your ManyToMany intermediary table, then order by that - for example
class ProfileViewed(models.Model):
viewed = models.ForeignKey('Profile')
viewer = models.ForeignKey('Profile')
date_added = models.DateField(auto_now_add=True)
class Profile(models.Model):
...
viewed = models.ManyToManyField('self', null=True, blank=True, related_name='viewed_profiles', symmetrical=False, through=ProfileViewed)
Then you can order your results like so:
profile = Profile.objects.get(...)
views = ProfileViewed.objects.filter(viewed=profile).order_by('date_added')

Django admin site -- list ForeignKey items as change-list on change page

In (a toy version of) my project, there are Owners who own any number of Objects. My models.py file looks like
class Owner(models.Model)
name = models.CharField(max_length=50)
date_of_birth = models.DateField()
class Object(models.Model)
name = models.CharField(max_length=50)
price = models.models.DecimalField(max_digits=9, decimal_places=2)
owner = models.ForeignKey(Owner)
My question relates to the change page for an Owner on the admin site, e.g.
http://mysite.com/admin/myapp/owner/1/.
Now I know that if I register Object as a TabularInline or a StackedInline, then I get an editable list of the Objects this Owner owns. However, in the real version of my project, an Object has something like 25 fields, not 2, and so neither of those options is really desirable aesthetically.
What I would really like instead is to essentially have a change-list of all the Objects an Owner owns appear on the Owner's change-page. (That way I get a nice compact listing of all the Owner's Objects, and if I need to edit the details of one, I can click on its link and edit it in its own page.) Basically I want the contents of
http://mysite.com/admin/myapp/object/?owner__id__exact=1
to appear within
http://mysite.com/admin/myapp/owner/1/.
Is there a way to do this?
PS: I'm using Django 1.4 and Python 2.7.
You can define what form class and/or fields to use in each InlineModelAdmin using these attributes, and limit the amount of input fields per object that way.

Django Admin: Dynamic Auto-Fill a Form Field from Selected Foreign Key's Attributes

I've got two simple models that are relevant to the question:
class CarMake(models.Model):
make = models.CharField(max_length=64)
default_cost = models.DecimalField(max_digits=7, decimal_places=2)
class Car(models.Model):
model = models.CharField(max_length=64)
make = models.ForeignKey(CarMake)
cost = models.DecimalField(max_digits=7, decimal_places=2)
When creating or updating a Car object in the Django Admin, I'd like the cost field to be auto-populated with the default_cost of the chosen CarMake that is selected from the drop-down.
Currently, I am storing the cost as the make's default_cost if the field is left blank on saving the object. I'd prefer to do this dynamically when the make drop down chooses a value. I assume that the solution would involve some amount of JavaScript...
Yes, if you want the selection to be updated dynamically you'd have to use JS. Doing this kind of stuff would involve hacking the django admin site. You may be able to get away with just customizing the template to load some JS with and event handler that updates the specific drop-down. However this would be vulnerable to breakage when updating Django (the admin site templates have no guarantee of backwards compatibility). Personally I'd just write a custom view.

Non-model field in Django model

I would like to have a model in Django that has multiple pictures associated with it. I'm evaluating possible options.
One picture for one model is easily done with the models.ImageField(...).
However, I would like a array (or set) of pictures. It can be just paths, not necessarily ImageField objects.
The problem is, how do I create that field in a Django model? I am assuming I will need to create a field that is not part of models.WhateverField. Is that possible? Can I define a non-model field, such as:
class MyModel:
name = models.CharField(max_length=10)
picture_list = []
and then do:
def sample_add_picture_view(request):
picture = "sample.jpg"
model = MyModel.objects.get(id=sample_id)
model.picture_list.append(picture)
model.save()
return HttpResponseRedirect('index.html')
Could this be done? If not, what could be a better solution? Thank you !
You need to create two separate models and link them with a ForeignKey field, like so:
class Item(models.Model):
name = models.CharField(max_length=255)
class ItemImage(models.Model):
image = models.ImageField(upload_to="item_images")
item = models.ForeignKey('Item', related_name="images")
It is possible to make a custom field to store multiple items, but it's a really bad idea. You would have to serialise an array into the database, making maintenance very difficult. Using a separate model means you can store extra information such as upload times, image captions etc with little extra effort.