Django - update another model on save in admin - python

I am using django to create a video library website.
I have 3 models for the library: Video, VideoTopic, and Course.
I have a TimedeltaField for the duration of each Video. Upon saving each video in admin, I want to get the sum of each TimedeltaField for all Videos within a Course, then save that sum to a TimedeltaField in my Course model.
I'm using django-timedeltafield, which will handle the summarization very well. What I have yet to find a solution for is how to update the Course model after I save each Video in admin. Any advice?
My models are as follows:
class Video(models.Model):
title = models.CharField(max_length=200)
topic = models.ForeignKey('VideoTopic')
duration = timedelta.fields.TimedeltaField(default="3 minutes, 30 seconds", help_text="(x minutes, x seconds)")
class VideoTopic(models.Model):
name = models.CharField(max_length=200, blank=True)
course = models.ForeignKey('course.Course')
class Course(models.Model):
name = models.CharField(max_length=200)
duration = timedelta.fields.TimedeltaField()

I recommend you use a django signal. This will ensure your models are synchronized regardless of where they are updated (in the app, admin, etc.).
Register the post save signal on the Video. In the signal, you will receive the video instance. Simply query for its related records in VideoTopic and Course and do the math there. I recommend doing all this in the transaction if you don't want it to ever fail. If you don't need the information always up-to-date, it's also an opportunity to do some background work to make the save lighter weight.
Why a signal?
Separates concerns
Ensures your logic always runs
Can help decouple things out of the model directly if it has dependencies, which can cut down on some situations of loading order/circular dependencies.
Alternatively, you could simply override the Video model's save method and do the same thing there. In either case, just use the related properties in the ORM from your foreign keys if you don't want to write the queries yourself.

An alternative is to override the admin save method to update the course model. See https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-methods.

Related

Django models. How do I make "add new" field in Django?

I am trying to make an OfferUp-like web app using Django Framework. Everything has been going great until I ran into a problem. How could I make it so that users can upload multiple pictures, instead of just one using the models.ImageField() function? You know? We might have users that only have 5 pictures to upload, while another user might have 8. How could I make it so that users can upload into the database as many pictures as they want?
What I'm going to suggest isn't that much different from the comment above (i don't have enough reputation to make a comment), so I'm just going to add a code snippet:
class Item(models.Model):
name = models.TextField()
class ItemImage(models.Model):
name = models.TextField()
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='images/')
and say: If you have more than one model with many images, rather than repeating the code you can just make a model (class) that will be inherited as the foreign key.

What is the best way to implement persistent data model in Django?

What I need is basically a database model with version control. So that every time a record is modified/deleted, the data isn't lost, and the change can be undone.
I've been trying to implement it myself with something like this:
from django.db import models
class AbstractPersistentModel(models.Model):
time_created = models.DateTimeField(auto_now_add=True)
time_changed = models.DateTimeField(null=True, default=None)
time_deleted = models.DateTimeField(null=True, default=None)
class Meta:
abstract = True
Then every model would inherit from AbstractPersistentModel.
Problem is, if I override save() and delete() to make sure they don't actually touch the existing data, I'll still be left with the original object, and not the new version.
After trying to come up with a clean, safe and easy-to-use solution for some hours, I gave up.
Is there some way to implement this functionality that isn't overwhelming?
It seems common enough problem that I thought it would be built into Django itself, or at least there'd be a well documented package for this, but I couldn't find any.
When I hear version control for models and Django, I immediately think of django-reversion.
Then, if you want to access the versions of an instance, and not the actual instance, simply use the Version model.
from reversion.models import Version
versions = Version.objects.get_for_object(instance)
I feel you can work around your issue not by modifying your models but by modifying the logic that access them.
So, you could have two models for your same object: one that can be your staging area, in which you store values as the ones you mention, such as time_created, time_modified, and modifying_user, or others. From there, in the code for your views you go through that table and select the records you want/need according to your design and store in your definitive table.

Using python builtins to pass django request from middleware to model in order to create default model filters for current user?

I have a fairly complex django application that has been in production for over a year.
The application holds data from different customers. The data is obviously in the same table, separated by customer_id.
Recently the client has started to ask questions about data segregation. Since the app is sold on a per user basis and holds sensitive information, customers have been asking if and how we maintain data segregation per customer, and are there any security measures that we take to prevent data leakages (ie. data from one customer being accessed by another customer).
We do our filters in the view endpoints, but eventually a developer in the team might forget to include a filter in his ORM query, and cause a data leakage
So we came up with the idea to implement default filters on our models. Basically whenever a developer writes:
Some_Model.objects.all()
essentially they will execute:
Some_Model.objects.filter(customer_id = request.user.customer_id)
We plan to achieve this by overriding the objects property on each model to point to a manager with a filtered queryset. Something like this:
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = request.user.customer_id
# the problem is that request.user is not available in models.py
)
class Some_Model(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(Customer)
objects = Allowed_Some_Model_Manager()
all_objects = models.Manager() # use this if we want all objects
However our problem is that request.user is not available in models.py.
I have found several ways to solve this.
Option 1 includes passing the request.user to the manager each time. However since I am dealing with thousands of lines of old code, I don't want to go and change all of our ORM queries.
Option 2, included using threading.local() to set the request.user in the thread local data.
Something like this: https://djangosnippets.org/snippets/2179/
There is a module that seems to be doing this: https://github.com/Alir3z4/django-crequest
However, a lot of people seem to be against this idea... Namely these two discussions:
django get_current_user() middleware - strange error message which goes away if source code is "changed" , which leads to an automatic server restart
Django custom managers - how do I return only objects created by the logged-in user?
So that brings me to Option 3 which I came up with, and I can not find anybody else using it. Use the python builtins module to pass the user from the middleware to the model.
#middleware.py
import builtins
def process_request(self, request):
if request.user.id:
builtins.django_user = request.user
#models.py
import builtins
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
if 'django_user' in vars(builtins):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = django_user.customer_id
)
else:
return super(Allowed_Some_Model_Manager, self).get_queryset()
I have tested the code and it is working on my local django server and on Apache with mod_wsgi. But I really want to hear if there are any pitfalls of this approach. I have never used builtins module before, and I am not sure if I understand how it works, and what is the use-case for it.

Ephemeral model instances for demo in Django 1.10

I need to dynamically create a series of models and relationships in a Postgres database for the purpose of demoing some features.
The issue is these models can't interfere with or be returned by queries in the normal functioning of the Django app. However they must have the full behavior set of the model and the ability to be returned by queries during the demo.
Using factories (we use Factory Boy primarily) is imperfect because the models can't be queried without being saved to the database at which point they interact with regular operation queries.
A second database is imperfect as well because the demo occurs during normal app operation and thus the "valid" model instances still need to be available.
Flagging the models as is_demo also won't work as there's a half dozen different types of models that need to be instantiated for the demo to work as intended.
I could clear the specific models that are created with a cron job or similar but they'd effect the database between creation and the scheduled job.
Running everything in a staging environment is acceptable, but the ideal answer allows me to run the demos on the production build.
Is there an accepted approach or useful library that I've missed?
A sample model that's involved in the demo:
class Transaction(models.Model):
amount = models.DecimalField(max_digits=8, decimal_places=2, db_index=True,
editable=False)
assigned_user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField(db_index=True, editable=False)
description = models.CharField(max_length=255, null=True, blank=True)
is_declined = models.BooleanField(default=False)
is_pending = models.BooleanField(db_index=True)
fi_account = models.ForeignKey(
FinancialAccount, on_delete=models.CASCADE)
#property
def organization(self):
org = self.fi_account.organization
return org
We'd need to be able to query this model and use its .organization property successfully. Without having it effect other queries.
You could create a clone of your production environment (database and django app), then update your production codebase to include the demo features/fixes. This way you'd have a copy of the "valid" production models in addition to the new models you're demoing.
It sounds like you should have a "staging" environment that closely matches your production environment for situations just like this.
Edit
One option I just thought of would be to create a Demo model which keeps track of records created as part of a demo. This Demo model would be have a ForeignKey to your User model, so each User can create/destroy their own demo (or your view can create it).
This would let you delete any records created as part of a specified Demo object.
However, you'd have to make sure you add an exclude for all of your current queries/filters of the models you have now.
Honestly, I'm not sure if what you're asking for can be done without exhaustively modifying all of your Model filters to exclude whatever demo records you create.

Django admin hangs (until timeout error) for a specific model when trying to edit/create

This one is driving me nuts right now. It was not happening before (even got screenshots I had to do for the user-manual since the customer required it).
I first noticed it on production server and then I checked and also happens in the dev server that comes with Django. The model appears on the main-page of the django admin, I can click it and it will display the list of point of sales. The problem comes whenever I want to edit an existing instance or create a new one.
I just click on the link (or put it on the bar) and it just hangs.
class PointOfSaleAdmin(admin.ModelAdmin):
list_display = ('id','business', 'user', 'zipcode', 'address','date_registered')
list_filter = ('business',)
filter_horizontal = ('services',)
admin.site.register(models.PointOfSale, PointOfSaleAdmin)
That's the registration of the model. All models are registered in the admin application and the user to test this is a super user. The model is:
class PointOfSale(models.Model):
user = models.ForeignKey(User)
zipcode = models.ForeignKey(Zipcode)
business = models.ForeignKey(Business)
services = models.ManyToManyField(Service,
verbose_name='available services')
date_registered = models.DateField(auto_now_add=True)
address = models.CharField(max_length=300)
Plus a few methods that shouldn't really matter much. Plus, last time before this that I tested the admin was right after I created all those methods, so it shouldn't matter on this.
The administrator very very rarely has to access this page. Usually it's just listing the PoS, but it still bothers me. Any idea of why it could be hanging? All other models are working just fine.
This is happening on both Django 1.2.5 and 1.3
EDIT:
I modified the timeout limits. It IS working, but somehow it takes several minutes for it to actually happen. So, there is something in the background that is taking ages. I don't understand how come it happens only for this model and it happens in different environments (and with small datasets)
I almost feel like slapping myself. My fault for not sleeping for so long.
The problem is that the zipcode list is pretty big (dozens of thousands) and the foreign key field is loaded as an html select tag, which means it loads every single entry. It's an issue with how much data there is simply.
Now I wonder how to control the way the foreign key is displayed in the admin. Anyone could help with that?
In your admin.py file, under the appropriate admin class, set
raw_id_fields = ('zipcode',)
This will display the zipcode's PK instead of a dropdown.
Is there a reason that you are setting up zipcode as it's own model instead of using a CharField or an actual zipcode modelfield?
I just wanted to add that another option here is creating a read_only_fields list. In cases where there is a relationship to a model with a large number of choices(in my case a rel table cataloging flags between a large number of users and discussion threads) but you don't need to edit the field. You can add it to the read_only_fields list will just print the value rather than the choices.
class FlaggedCommentsAdmin(ModelAdmin):
list_display = ('user', 'discussion', 'flagged_on')
readonly_fields = ('user', 'discussion')
For people still landing on this page: As Mamsaac points out in his original post, the timeout happens because django tries to load all instances of a ForeignKey into an html-select. Django 2 lets you add an auto-complete field which asynchronously lets you search for the ForeignKey to deal with this. In your admin.py do something like this:
from django.contrib import admin
from .models import Parent, Child
#admin.register(Parent)
class ParentAdmin(admin.ModelAdmin):
# tell admin to autocomplete-select the "Parent"-field 'children'
autocomplete_fields = ['children']
#admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
# when using an autocomplete to find a child, search in the field 'name'
search_fields = ['name']
Have you tried checking the apache logs (if you're using apache obviously) or any other HTTP server related logs? That might give you an idea of where to start.
That's the only model that is affected? You mentioned methods on the model. Try commenting out those methods and trying again (including the __unicode__ method), just to see if they somehow affect it. Reduce everything down to the bare minimum (as much as possible obviously), to try and deduce where the regression started.
Try to monitor server resources when you request this page. Does CPU spike dramatically? What about network I/O? Could be a database issue (somehow?).
Sorry this doesn't really answer your question, but those are the first debugging techniques that I'd attempt trying to diagnose the problem.

Categories

Resources