django-reversion revert ManyToMany fields outside admin - django

I am using django-reversion in my project.
And it works good except one thing:
I can't get previous versions of ManyToMany fields. But in django admin it is works, not in my code.
To get previous version I use following code:
vprod = Version.objects.get_for_date(product, ondate).get_object_version().object
and it works except m2m field
where 'product' is object of Product class,
class Product(models.Model):
name = models.CharField(max_length=255)
elements = models.ManyToManyField(Sku)
class Sku(models.Model):
name = models.CharField(max_length=255, verbose_name="SKU Name")
I can get vprod.name and it returns what I need, but when I try vprod.elements.all() it returns list only the current (last) version, even if the number of elements changed.

If I understand it correctly, I think you should get the revision for the version; the version contains the data of the object, the revision contains versions for multiple objects. Have a look at:
some_version.revision.version_set.all()
Concretely, I think you should use (untested):
[
v for v in Version.objects.get_for_date(product, ondate).revision.version_set.all()
if version.content_type == ContentType.objects.get_for_model(Sku)
]
Note, btw, that reversions should know that it should follow relationships. Using the low level API:
reversion.register(YourModel, follow=["your_foreign_key_field"])

I had the same issue and thanks to #Webthusiast's answer I got my working code. Adapting to your example would be something like this.
Imports:
from django.contrib.contenttypes.models import ContentType
import reversion
Register your models:
reversion.register(Sku)
reversion.register(Product, follow=['elements'])
And then you can iterate:
object = Product.objects.get(some_id)
versions = reversion.get_for_object(self.object)
for version in versions:
elements = [v.object_version.object \
for v in version.revision.version_set.all() \
if v.content_type == ContentType.objects.get_for_model(Product)]
The documentation for this is now on Read the Docs. Refer to the 'Advanced model registration' section of the Low-level API page.

Related

Updating JSONField in django rest framework

I currently became familiar with using JSONField in django rest-framework, but I could not find any straight forward way to update a key in a stored json. There are many ways to filter JSONField depends on its internal keys, but it seems that there is no way to change, update or delete a key from already stored JSONField. But it seems that postgres can do some modifications on json keys as this answer explained.
Is there any function which is able to do modifications on JSONFields. If there is not any direct command to do this, what is the best way to implement modifications of a JSONField?
Edit:
As an example if I have a model like this:
class Thing(models.Model):
name = models.CharField()
properties = JSONField()
And in properties I stored a json like this :
{
"color" : "red",
"size" : "large",
"cost" : 1234
}
Then I want to change the color to "green" by using django commands.
thing = Thing.objects.get(name="...")
thing.properties['color'] = 'green'
thing.save()
The approach with jsonb_set from #tarasinf and #Vladius001 does also work with Django's Func expression.
from django.db.models import F, Func, Value, JSONField
Thing.objects.update(
properties=Func(
F("properties"),
Value(["color"]),
Value("green", JSONField()),
function="jsonb_set",
)
)
For your model:
class Thing(models.Model):
name = models.CharField()
properties = JSONField()
If you need to update bunch of entries, you can do the following:
Thing.objects.update(
properties=RawSQL("jsonb_set(properties, '{color}', 'green', true)", [])
)
color: green will be inserted, if not exists.
Welcome to PostgreSQL docs for more information about jsonb_set.
The next syntax works for me,
Django==2.2.19
Postgres==12.0.6
from django.db.models.expressions import RawSQL
Thing.objects.update(properties=RawSQL("""jsonb_set(properties, \'{"name"}\',\'"name value"\', true)""", []))
You can find better solution here
https://django-postgres-extensions.readthedocs.io/en/latest/json.html

Error database query using ManyToManyField on model

I'm fairly new to django web development. And I got an error whereby I try to change a 'post' under admin url - so localhost:8080/admin. I'm able to create it successfully but when I try to click the post that I had just added. I'm getting this error:
Exception Type: DatabaseError Exception Value: This query is not
supported by the database.
And this is the code that I know is 'messing' with this query:
#Post is an abstract class
class BlogPost(Post):
...
translators = models.ManyToManyField(Staff, related_name='translators')
photographers = models.ManyToManyField(Staff, related_name='photographers')
authors = models.ManyToManyField(Staff, related_name='authors')
...
To explain what is going on with this blog post - it can have multiple 'owners'/people that contributed to this post and thus the decision using ManyToManyField. And vice-versa with the 'Staff' member - the type of 'member' can have multiple ownership on multiple posts (Let me know if this logic doesn't make any sense because it does to me).
I'm using mongodb for the database, django 1.5.11 and I have installed djangotoolbox. I've tried the following solutions with adding a relationship to BlogPost as shown below:
Class Staff(Member):
...
staff_posts = models.ManyToManyField(BlogPost, related_name="staff_posts")
...
But I'm getting an error on 'cannot import BlogPost'. I tried figuring out the reason of this error and I don't think that I have a circular dependance - after checking all of the files, there's no circular dependance.
MongoDB (or mongoengine, which I'm guessing you're using) doesn't support joins, so the typical way to model many-to-many relations in a relational database has to be implemented some other way.
One way is to use a ReferenceField inside a ListField. It might look like this (not tested):
class BlogPost(Post):
authors = models.ListField(models.ReferenceField(Staff))
...
Also see these answers:
https://stackoverflow.com/a/18747306/98057
https://stackoverflow.com/a/25568877/98057
Just to put it out there, I'm not real familiar with MongoDB.
However, I don't believe you need to define a ManyToManyField on your Staff class. You already have a ManyToMany defined in your BlogPost, having it defined in one class file is all that is required. (At least for MySQL).

Django: Querying comments based on object field

I've been using the built-in Django comments system which has been working great. On a particular page I need to list the latest X comments which I've just been fetching with:
latest_comments =
Comment.objects.filter(is_public=True, is_removed=False)
.order_by('submit_date').reverse()[:5]
However I've now introduced a Boolean field 'published' into the parent object of the comments, and I want to include that in the query above. I've tried using the content_type and object_pk fields but I'm not really getting anywhere. Normally you'd do something like:
Comment.objects.filter(blogPost__published=True)
But as it is not stored like that I am not sure how to proceed.
posts_ids = BlogPost.objects.filter(is_published=True).values_list('id', flat=True) #return [3,4,5,...]
ctype = ContentType.objects.get_for_model(BlogPost)
latest_comments = Comment.objects.filter(is_public=True, is_removed=False, content_type=ctype, content_object__in=posts_ids).order_by('-submit_date')[:5]
Comments use GenericForeignKey to store the relation to parent object. Because of the way generic relations work related lookups using __<field> syntax are not supported.
You can accomplish the desired behaviour using the 'in' lookup, however it'll require lot of comparisons when there'll be a lot of BlogPosts.
ids = BlogPost.objects.filter(published=True).values_list('id', flat=True) # Get list of ids, you would probably want to limit number of items returned here
content_type = ContentType.objects.get_for_model(BlogPost) # Becasue we filter only comments for BlogPost
latest_comments = Comment.objects.filter(content_type=content_type, object_pk__in=ids, is_public=True, is_removed=False, ).order_by('submit_date').reverse()[:5]
See the Comment model doc for the description of all fields.
You just cannot do that in one query. Comments use GenericForeignKey. Documentation says:
Due to the way GenericForeignKey is implemented, you cannot use such
fields directly with filters (filter() and exclude(), for example) via
the database API.

How can I get access to a Django Model field verbose name dynamically?

I'd like to have access to one my model field verbose_name.
I can get it by the field indice like this
model._meta._fields()[2].verbose_name
but I need to get it dynamically. Ideally it would be something like this
model._meta._fields()['location_x'].verbose_name
I've looked at a few things but I just can't find it.
For Django < 1.10:
model._meta.get_field_by_name('location_x')[0].verbose_name
model._meta.get_field('location_x').verbose_name
For Django 1.11 and 2.0:
MyModel._meta.get_field('my_field_name').verbose_name
More info in the Django doc
The selected answer gives a proxy object which might look as below.
<django.utils.functional.__proxy__ object at 0x{SomeMemoryLocation}>
If anyone is seeing the same, you can find the string for the verbose name in the title() member function of the proxy object.
model._meta.get_field_by_name(header)[0].verbose_name.title()
A better way to write this would be:
model._meta.get_field(header).verbose_name.title()
where header will be the name of the field you are interested in. i.e., 'location-x' in OPs context.
NOTE: Developers of Django also feel that using get_field is better and thus have depreciated get_field_by_name in Django 1.10. Thus I would suggest using get_field no matter what version of Django you use.
model._meta.get_field_by_name('location_x')[0].verbose_name
You can also use:
Model.location_x.field.verbose_name
Model being the class name. I tested this on my Animal model:
Animal.sale_price.field.verbose_name
Animal.sale_price returns a DeferredAttribute, which has several meta data, like the verbose_name
Note: I'm using Django 3.1.5
If you want to iterate on all the fields you need to get the field:
for f in BotUser._meta.get_fields():
if hasattr(f, 'verbose_name'):
print(f.verbose_name)
# select fields for bulk_update : exclude primary key and relational
fieldsfields_to_update = []
for field_to_update in Model._meta.get_fields():
if not field_to_update.many_to_many and not field_to_update.many_to_one and not field_to_update.one_to_many and not field_to_update.one_to_one and not field_to_update.primary_key and not field_to_update.is_relation :
fields_to_update = fields_to_update + [field_to_update.name]
Model.objects.bulk_update(models_to_update , fields_to_update)

Django admin doesn't show translated enumerations in list view under Python 2.3

When using localized list of "choices" for a model field, the admin doesn't show the translated values in the list view.
Short example:
from django.utils.translation import ugettext_lazy as _
class OrderStates:
STATES = (
(STATE_NEW, _("New")),
(STATE_CANCELLED, _("Cancelled")), )
class Order(models.Model):
state = models.IntegerField(choices=OrderStates.STATES)
# ..
class OrderAdmin(admin.ModelAdmin):
list_display = [ 'id', 'state', 'address', 'user']
# ..
admin.site.register(Order, OrderAdmin)
The localized versions of "New" and "Cancelled" show up correctly in the front-end and in the admin form when editing an order. But in the admin list view I get blank fields - regardless of the language I switch to, including English. Column names are fine.
This only happens with Python 2.3 (talk about niche questions). The choices display correctly everywhere with Python 2.5. I don't get any errors or warnings in neither.
Tried using ugettext instead of ugettext_lazy for the options, which didn't work. ugettext_noop sort of works - it at least shows the original english versions instead of blank fields.
Am I doing something wrong or is this a bug?
This is probably a bug somewhere in Django, not calling force_unicode on the item correctly. The original code you pasted is correct. You don't mention what Django version you're using, so I'd reccomend trying the latest 1.0.3 or 1.1 release to see if that happens to fix it, else check the ticket tracker to see if it's already been reported (note that if it hasn't been fixed yet it probably won't be at all, since 1.1 is the last version to support 2.3).
try using:
import gettext as _
Though, that may break if some of your translations use non-ascii values. Actually, this should have been fixed some time ago, see Ticket #5287.
Hope this helps.