Extending a field of a contrib model in Django - django

I have come across this situation several times.
If I have something that I generally like in contrib, but I want to make one minor tweak to a field, what do I do?
I don't want to throw out the baby with the bathwater.
To give an example, take auth.user (which, contrary to what seems to be popular opinion on the matter, I regard as being generally on the right track). I want to create a through model for auth.user's relationship to auth.group.
How can I do this without modifying django?

auth.User is a special case since the User model is tied in to lots of parts of Django and it is tricky to modify this (though not impossible, as others have pointed out). My best advice would be to question why you don't want to modify Django source. You can pull source for either the head of the devel branch or get a tagged version corresponding to a numbered release. Modify the code at will and use some combination of svn update, svn diff, and svn patch to migrate your changes.
Next, modifications to contrib modules are possible in your code, since Python is interpreted and dynamically typed. If you do this, you'll need to take into consideration parsing/processing order since some operations may already have utilized the original module. Below is an example I got from someone else (probably here on SO) of how to add a convenient forward reference from User to the associated Profile object:
from django.db.models import Model
from django.contrib.auth.models import User
class UserProfile(Model):
user = ForeignKey(User, unique=True)
phone = CharField(verbose_name="phone number", blank=False, max_length='20')
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
I don't think this strategy will work for adding/modifying ModelFields in django.contrib.auth.models.User, however.
Finally, for your specific example of associating groups with a user, you should see if this is possible by creating a UserProfile model. My initial guess is that it should be pretty easy.

Related

Is there a Django ManyToManyField with implied ownership?

Let's imagine I'm building a Django site "CartoonWiki" which allows users to write wiki articles (represented by the WikiArticle-model) as well as posting in a forum (represented by the ForumPost-model). Over time more features will be added to the site.
A WikiArticle has a number of FileUploads which should be deleted when the WikiArticle is deleted. By "deleted" I mean Django's .delete()-method.
However, the FileUpload-model is generic -- it's not specific to WikiArticle -- and contains generic file upload logic that e.g. removes the file from S3 when it's removed from the database. Other models like ForumPost will use the FileUpload-model as well.
I don't want to use GenericForeignKey nor multi-table inheritance for the reasons Luke Plant states in the blog post Avoid Django's GenericForeignKey. (However, if you can convince me that there really is no better way than the trade-offs GenericForeignKey make, I might be swayed and accept a convincing answer of that sort.)
Now, the most trivial way to do this is to have:
class FileUpload(models.Model):
article = models.ForeignKey('WikiArticle', null=True, blank=True, on_delete=models.CASCADE)
post = models.ForeignKey('ForumPost', null=True, blank=True, on_delete=models.CASCADE)
But that will have the FileUpload-model expand indefinitely with more fields -- and similar its underlying table will gain more and more columns as new models in the system start using FileUpload. This feels suboptimal both in terms of data-modeling, but also in terms of separation-of-concerns -- the FileUpload-model and table is being changed while no actual new functionality is being added to it.
My preference would really be to go the other way around:
class WikiArticle(models.Model):
uploads = models.ManyToManyField('FileUpload')
But this doesn't solve the deletion issue: If I .delete() a WikiArticle the corresponding FileUploads won't be deleted. I've tried various setups with through-models, but none seem to solve it. What I really need is a OneToMany-field -- a sort of reverse ForeignKey to indicate the ownership in the right direction without polluting the generic/reusable model.
Should FileUpload really instead be a field? Or perhaps an abstract model? (WikiArticleFileUpload, ForumPostFileUpload, and so on...).
I realize that a true ManyToManyField with implied ownership would no longer really be a ManyToManyField since the field implies sharing. E.g. a FileUpload could technically be referenced by multiple WikiArticles, so you could be removing FileUploads from other objects rather on top of the one you're deleting. The question still stands though -- it seems I need a OneToManyField to model this in a nice way.
You probably have a couple of options to solve your problem, but it also requires on the exact requirements of your application.
Using a GenericForeignKey in this situation is probably fine, escpecially due to the fact that you do not know how many other models will use your upload model. Of course as mentioned in the linked blog post eg. doing plain SQL queries might be harder but it's on you to decide if that's a problem for your use case.
Also using inheritance might be an option, so that all the referenced models inherit the relation to the upload model from a common ancestor. This might have a small impact performance-wise because you Django would need to join the tables of the models but the impact might still be not that big. On the other hand this approach might also have some advantages if eg. your articles and posts have other stuff in common as well and you could easily do stuff like "show all new posts and articles (together)".
If you handle deletion yourself as mentioned in the previous answer you can also add ManyToMany fields yourself but also consider that this method also has some disadvantages in common with using generic foreign keys (eg. a lot of stuff to join in the database...)
Probably it's fine that you just use a GenericForeignKey, especially if the number of models that use your "generic" model gets bigger (eg. more than 3-5). All in all this sounds pretty much like a use case GenericForeignKey was made for (imagine the uploads being something like "tags" belonging to the posts).
ManyToMany fields are symmetrical, even though you define them on one model with an (explicit or implicit) related_name on the other.
I can think of two methods to clean up while, or after, WikiArticles are deleted. The first is to periodically search for and delete "orphan" FileUploads. At its simplest, (assuming a related_name of articles)
deleted = FileUpload.objects.filter( articles__isnull=True).delete()
The other is to explicitly process the related articles during deleting of the article. It's straightforward to subclass the object's delete method, but this is not the only way to delete an object (bulk_delete, for example, bypasses this). Anyway,
def delete( self, *args, **kwargs):
article_pks = self.uploads.all().values_list('pk', Flat=True)
response = super().delete( *args, **kwargs)
FileUpload.objects.filter(
pk__in = article_pks, articles__isnull=True) .delete()
return response
(or even just execute the "periodically" code above, for every article-deletion, which will also tity after any deleted though othr channels)
Please thoroughly test this if you use it. Delete operations which don't do precisely what is wanted are the scariest sorts of bug!

Django: Two models with OneToOneField vs a single model

Let's imagine a I have a simple model Recipe:
class Recipe(models.Model):
name = models.CharField(max_length=constants.NAME_MAX_LENGTH)
preparation_time = models.DurationField()
thumbnail = models.ImageField(default=constants.RECIPE_DEFAULT_THUMBNAIL, upload_to=constants.RECIPE_CUSTOM_THUMBNAIL_LOCATION)
ingredients = models.TextField()
description = models.TextField()
I would like to create a view listing all the available recipes where only name, thumbnail, preparation_time and first 100 characters of description will be used. In addition I will have a dedicated view to render all remaining details for a single recipe.
From the efficiency point of view, since description may be a long text, would it make sense to store the extra information in a separate model, let's say 'RecipeDetails' which would not be extracted in a list view but only in a detailed view (maybe using prefetch_related method)? I am thinking about something along:
class Recipe(models.Model):
name = models.CharField(max_length=constants.NAME_MAX_LENGTH)
preparation_time = models.DurationField()
thumbnail = models.ImageField(default=constants.DEFAULT_THUMBNAIL, upload_to=constants.CUSTOM_THUMBNAIL_LOCATION)
description_preview = models.CharField(max_length=100)
class RecipeDetails(models.Model):
recipe = models.OneToOneField(Recipe, related_name="details", primary_key=True)
ingredients = models.TextField()
description = models.TextField()
In my recent online searches people seem to suggest that OneToOneField should be used only for two purposes: 1. inheritance and 2. extending existing models. In other cases two models should be merged into one. This may suggest I am missing something here. Is this a reasonable use of OneToOneField or does it only add to a complexity of an overall design?
inheritance
Don't do that, because inheritance would only be useful if you have baseclass/subclass relationship. The classic example is animal and cat/dog, in which the cats/dogs all have some basic properties that could be extracted, but your Recipe and RecipeDetail don't.
From the efficiency point of view, since description may be a long
text, would it make sense to store the extra information in a separate
model
Storing extra information in a separate model doesn't improve any efficiency. The underline database would create something like a ForeignKey field and plus unique=True to make sure the uniqueness. As far as I concerned, OneToOneField is only useful when your original model is hard to change, e.g., it is from third-party packages or some other awkward situations. Otherwise I still consider adding them to the Recipe model. In this case, you can manage your model easily while avoiding having some extra lookups like recipe.recipedetail.description, you can just do recipe.description.
No, it's not reasonable to split your Recipes. First, your model should contain all properties for being a "Recipe" (and a recipe without ingredients is not a recipe at all). Second, if you want to improve performance, then use the Django's Cache Framework (it was created exactly for improving performance issues). Third, keep it simple and do not over-engineering your development cycle. Do you really need to improve performance right now?
Hope it helps!
First mistake in development, you are thinking in efficiency before your first version is running.
Try to have now a first version, that runs, and later you can think in be more faster based in use cases with your first version. After this you can check if a model and relations, or only a new field in model or using Django Cache for views can do the work.
Your think in efficiency first will be "de-normalize" your Database btw, when one update in the model with full description is done, you need to launch one update to the model with "description-preview" field. trigger in database level? python code for update in app level? nightmares in code design ... before your code runs.

Tracking a reverse relationship for a foreignkey in django-reversion

I'm trying to figure out how how to track changes for a foreignkey relationship in Django using Django-reversion.
In short, I am trying to model a Codelist, which contains Codes which only belong to one Codelist. This can be modelled using a foreign key like so:
class CodeList(models.Model):
name = models.CharField(max_length=100)
class Code(models.Model):
value = models.PositiveIntegerField(max_length=100)
meaning = models.CharField(max_length=100)
codelist = models.ForeignKey(CodeList,related_name="codes")
Additionally, the only way to edit a code is by using an inline form in the admin site accessed via its codelist. For all intents and purposes, codes belong to codelists as they should...
Except when it comes to reversion.
I'm using the reversion.middleware.RevisionMiddleware to track all editing changes, as there are some non-admin forms for editing codes.
What I'd like is when I see the history of a codelist, it should changes to the codes as well, but I can't figure that out in the Django-reversion API. The issue is that the API covers tracking the code, and seeing changes to the codelist, not the other way around by following the reversed relationship.
Is anyone aware of how this might be done?
Its not well documented Its very well documented, I just couldn't find it, but you can just add the inverse relationship as the field to follow like so:
reversion.register(CodeList, follow=["codes"])

How to replace a foreignkey with another using a migration

I'd like to replace an existing ForeignKey pointing at my User model with one pointing at a profile model.
The change in the model is:
created_by=models.ForeignKey(settings.AUTH_USER_MODULE)
To:
created_by=models.ForeignKey(settings.PROFILE_MODEL)
The auto-generated migration looks like (with constants subbed in):
migrations.AlterField(
model_name=MODEL,
name='created_by',
field=models.ForeignKey(to=settings.PROFILE_MODEL),
preserve_default=True,
),
I also have ManyToManyFields to deal with as well. What I have in my head is I'd like a function to run on each MODEL object to resolve the user object to the profile object. How would I go about doing this?
The relationship between user and profile is (and vice versa):
User.profile = Profile
Edit: Forgot to mention, if the auto-generated migration is run you get the following error:
ValueError: Lookup failed for model referenced by field
APP1.MODEL.created_by: APP2.PROFILE_MODEL
As I understand now you want to migrate only our app without expecting anything to be changed to the global auth User model. Then it's easy. Migrations work nice with symbolic settings names.
I tried it with Django 1.7. It is possible to switch between settings.AUTH_USER_MODEL and settings.PROFILE_MODEL back and forth without any problem. A migration can be created and applied after every change. The tested model had also a ManyToManyField and mutual relationships between User and Profile.
I see you have APP1 and APP2. Maybe you make migrations for both and they are circular dependent so that a part of other application migration should be applied before the current one migration can be completely applied and vice versa. It can be simplified by spliting a change to more smaller and making automatic migrations after every change so that they are less dependent. A OneToOneField is better than two mutual foreign keys and its reverse relation is even so useful. A foreign key can be changed temporarily to IntegerField(null=True) in the worst case in order to simplify data migration. It is really viable more or less nice.
The question looked nice initially, but the problem should be better specified to be reproducible.
Edited by removing the original text after reading information in comments:

django model versioning/revisions/approval - how to allow user to edit own profile but keep old online until new approved?

I am building a site where users can make changes to their publicaly displayed profile. However I need all changes to be approved by an admin before going live. Until the changes are approved their old profile will be displayed. In the admin there should be a list of profiles awaiting approval. It is preferable, but not required, to keep a history of versions.
I have looked at django-reversion, but don't think that will handle showing an old version while keeping a new one under-approval.
I'm looking for ways to achieve this with django...
Two from-the-hip ideas. How about...
Use reversion and add logic which auto-marks a profile as 'unapproved' on save() if the save is not performed by an administrator, then add a custom accessor to your code that gets the latest approved profile from the reversion archive.
Or, if reversion won't play nicely, have a 'current profile' and 'pending profile' for each user and update the FKs when the profile is approved...
This apps do exactly what you need
http://github.com/dominno/django-moderation
I've had some problems using django-moderation from dominno, which are:
Using a unique model for tracking changes in several others, with a GenericForeignKey reduces the amount of tables needed to monitor things, but it's a pain to manage. And I don't trush GenericForeignKeys for this type of task.
Deserialization of sandboxed values would invariably fail if I changed one field's name in the model. (for example, if I migrated a field change of name after monitoring it in moderation). It should at least be able to recover non bogus field values.
So I made my own app, which tackles the problems mentioned above.
It should give you what you're looking for.
https://github.com/artscoop/django-approval
It has auto approval mechanism, field selection (you can always ignore some fields, and put others to validation) and default values (for example, automatically set an object to hidden when it's created, so that it can be moderated without being visible in the first place)