How to make import-export save JSONField not as string - django

I'm trying to import JSONField using django import-export, it keeps saving JSON as string (adding "" to it)
models.py
from django.db import models
from django.contrib.postgres.fields import JSONField
class Governorate(models.Model):
name = models.CharField(max_length=500)
data = JSONField()
def __str__(self):
return ("%s" %(self.name))
admin.py
from django.contrib import admin
from .models import Governorate
from import_export.admin import ImportExportModelAdmin
from import_export import resources
class GovernorateResource(resources.ModelResource):
class Meta:
model = Governorate
class GovernorateAdmin(ImportExportModelAdmin):
list_display = ('id','name', 'data')
resources_class = GovernorateResource
admin.site.register(Governorate,GovernorateAdmin)
I expected the output to be: {"xx":{"xx":"xx","xx":"xx"} however it saves it as "{"xx":{"xx":"xx","xx":"xx"}"
Tried uploading XLSX and CSV.

Version 1.2.0 of import-export doesn't automatically recognise the JSONField in your Resource so it just defaults to a CharField type. It's already added to the master branch but not released.
So just override the field to use the JSONWidget which is already available in version 1.2.0:
from import_export import fields, widgets
class GovernorateResource(resources.ModelResource):
data = fields.Field(widget=widgets.JSONWidget())
class Meta:
model = Governorate

So basically, the import-export library was saving the JSONField() as a string. So my solution was to create a signal to check if the instance was a string or a dict. then fix it.
I tried to create a Field with the JSONWidget widget, but doesn't work.
models.py
#receiver(post_save, sender=Governorate)
def fix_json(sender, instance, **kwargs):
if (type(instance.data) is str):
instance.data = eval(instance.data)
instance.save()
print(instance.data)
The Django import-export library is really good, but lacks proper documentation to be honest.

Related

Django model returns None in AppConfig

I am trying to fetch values from mysql database using django model inside an appconfig subclass but I keep getting None.
class myappConfig(AppConfig):
name = 'myapp'
def ready(self):
from .models import myModel
mydata = myModel.objects.values('A')
Even though there is data in the mysql table corresponding to myModel, the value in mydata is None.
What could be the reason for this ?

Django-Oscar form fork - Error unknown fields (but fields are in the models)

I am trying to fork django-oscar to change the dashboard form for product attributes, multioption. Need to have a description field for every option.
project/oscar_fork/catalogue/models.py:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from oscar.apps.catalogue.abstract_models import AbstractAttributeOption, AbstractAttributeOptionGroup
class AbstractAttributeOption(AbstractAttributeOption):
description = models.CharField(_('Description'), max_length=250, blank=True)
group = models.ForeignKey(
'catalogue.AttributeOptionGroup',
on_delete=models.CASCADE,
related_name='optionsblabla',
verbose_name=_("Group"))
from oscar.apps.catalogue.models import *
The models are changed with the extra field "description" in my DB, but still my form field returns cannot find this field.
project/oscar_fork/dashboard/catalogue/forms.py:
from oscar.apps.dashboard.catalogue import forms as base_forms
class AttributeOptionForm(base_forms.AttributeOptionForm):
class Meta(base_forms.AttributeOptionForm.Meta):
fields = ('option', 'description')
If I change the form.py and models.py fields directly in the Oscar app it works. Other forms can be forked that easily as shown above. Tried it with AttributeOptionGroupForm. I think there is a problem with the sequence of import. How can I solve this?
Error:
django.core.exceptions.FieldError: Unknown field(s) (description) specified for AttributeOption
I am using django-oscar v1.6. Django v.2.08.
Your concrete model should be named AttributeOption without the 'Abstract', otherwise oscar will not pick it up and use its own AttributeOption model instead, which has no description:
class AttributeOption(AbstractAttributeOption):
description = ...
You will have to run makemigrations and migrate after that.
Check the source code of the models module that you import at the end. You will see how their dynamic model loading works:
if not is_model_registered('catalogue', 'AttributeOption'):
class AttributeOption(AbstractAttributeOption):
pass

DRF: JSONField in serializers with TextField in models cause stringification

I am using python 2.7.11
A have a model let's say Game that has a TextField that's supposed to store json values. TextField was chosen because the database is shared with hibernate ORM that doesn't support postgres JSONb natively. Thus I have:
models.py:
#python_2_unicode_compatible
class Game(models.Model):
settings = models.TextField(default='{}')
serializers.py:
class GameSerializer(serializers.ModelSerializer):
settings = serializers.JSONField()
Is there a clean way to handle this, having valid json strings in the database and returning them as json objects through the API?
You can try something like:
import json
#python_2_unicode_compatible
class Game(models.Model):
settings = models.TextField(default='{}')
#property
def settings_json(self):
return json.loads(self.settings)
And then in the serializer:
class GameSerializer(serializers.ModelSerializer):
settings = serializers.JSONField(source="settings_json")

Dynamical choices in model's field

I want my models to have order field, which will contain order of an item among all items of its kind.
And I want to use choices within that IntegerField, which would contain all the numbers of currently existing items in that table.
So it would need to be dynamic choices.
How do I load all existing "order" values of all existing items in a table, and use this list for choices?
It sounds like you want to build a manager for your model:
models.py
from django.db import models
class OrderManager(models.Manager):
def order_choices(self):
return [(i, i) for i in OrderModel.objects.values_list('order', flat=True)]
class OrderModel(models.Model):
objects = OrderManager()
order = models.IntegerField()
class Meta:
ordering = ['order']
def __unicode__(self):
return '%i' % self.order
forms.py
from django import forms
from yourapp.models import OrderModel
class OrderModelForm(forms.ModelForm):
order = forms.ChoiceField(choices=OrderModel.objects.order_choices())
class Meta:
model = OrderModel
admin.py
from django.contrib import admin
from yourapp.forms import OrderModelForm
from yourapp.models import OrderModel
class OrderModelAdmin(admin.ModelAdmin):
form = OrderModelForm
admin.site.register(OrderModel, OrderModelAdmin)
Edit
Managers are use to make general model queries without having an instance of a model object. If you don't understand the concept of managers, you can still refactor the code out of the manager class, stick it somewhere else and import that function across your code. Managers allow you to abstract custom general queryset that you can reuse. See more details https://docs.djangoproject.com/en/dev/topics/db/managers/
The code without the manager will look like
views.py or some other file
from app.models import OrderModel
def order_choices():
return [(i, i) for i in OrderModel.objects.values_list('order', flat=True)]
From anywhere in your code, if you want to reuse the above multiple times:
from app.views import oder_choices
order_choices()
as opposed to:
from app.models import OderModel
OrderModel.objects.order_choices()
If you only want to use the above once, you can leave it in the forms.py as shown in the other answer. It's really up to you on how you want to refactor your code.
Dont add the choices directly to the model, add them to a form represnting the model later, by overriding the field with a set of choices.
than, do something like:
class MyForm(..):
myfield_order_field = IntegerField(choices = [(i,i) for range(MyModel.objects.count)])
class Meta():
model = MyModel
if you want to use it in the admin, add to your Admin Class:
class MyModelAdmin(admin.ModelAdmin):
...
form = MyForm
it will override this field in the admin too.

django - Joining LogEntry to actual models

so i'm using the admin LogEntry object/table to log events in my app. I have a view where i'd like to display each LogEntry.
It would be really great if i could join the LogEntry with the actual objects they represent (so i can display attributes of the object inline with the log entry)
In theory this should be easy as we have the model type and id from the LogEntry but i can't figure out how to join them using a queryset.
i thought i could just grab all the ids of the different objects and make another dictionary for each object type and then join them somehow (maybe zip the lists together?) but that seems dumb and not very djano-ish/pythonic.
does anybody have better suggestions?
** edit **
just want to clarify am not looking to use admin, but roll a custom view and template.
As I know Django uses contenttypes framework to perform logging in admin. So you should create generic relation inside your model and then to show inlines in admin use GenericTabularInline and GenericStackedInline. Please consult with the article.
from django.contrib import admin
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.generic import GenericTabularInline
from django import forms
from some_app import models
from some_app.models import Item
class LogForm(forms.ModelForm):
class Meta:
model = LogEntry
class LogInline(GenericTabularInline):
ct_field = 'content_type'
ct_fk_field = 'object_id'
model = LogEntry
extra = 0
class ItemForm(forms.ModelForm):
class Meta:
model = Item
class ItemAdmin(admin.ModelAdmin):
form = ItemForm
inlines = [LogInline,]
admin.site.register(models.Item, ItemAdmin)
and you add to Item:
class Item(models.Model):
name = models.CharField(max_length=100)
logs = generic.GenericRelation(LogEntry)
this change won't create anything in your database, so there is no need to sync
Recent Django versions require to create a proxy for LogEntry:
from django.contrib import admin
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.generic import GenericTabularInline
class LogEntryProxy(LogEntry):
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
proxy = True
class LogInline(GenericTabularInline):
model = LogEntry
extra = 0
class ItemAdmin(admin.ModelAdmin):
inlines = [LogInline,]
admin.site.register(models.Item, ItemAdmin)