how to exclude django hstore field in admin? - django

The standard approach in admin.py by defining class:
exclude = ('some_hstore_field', )
does not work.
I manage to get expected result by specifying explicit fields but would like rather exclude the one I don't need, than specify all the other needed.

Assuming you have in your model hstore field called facilities:
facilities = hstore.DictionaryField(schema=HSTORE_SCHEMA)
then you CAN NOT just write:
exclude = ('some_non_hstore_field', 'facilities')
Assuming your hstore schema looks something like that:
HSTORE_SCHEMA = [
{
"name": "vegetarian_menu",
"class": "BooleanField",
"kwargs": {
"default": False,
"verbose_name": "vegetarian menu"
}
},
{
"name": "vegan_menu",
"class": "BooleanField",
"kwargs": {
"default": False,
"verbose_name": "vegan menu"
}
}
]
You have to exclude each of subfield by its name, e.g:
exclude = ('some_non_hstore_field', 'vegetarian_menu', 'vegan_menu')
you can do it like this:
exclude = tuple(['some_non_hstore_field'] + [field['name'] for field in HSTORE_SCHEMA])
or like this - using meta of class field:
exclude = tuple(['some_non_hstore_field'] + [field['name'] for field in YourModel._meta.get_field_by_name('facilities')[0].schema])
The same applies for readonly_fields

Related

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

Django annotate several same objects in QuerySet by different related object

I got:
# models
class Building(models.Model):
...
class Flat(models.Model):
building = models.ForeignKey(Building)
class Profile(models.Model):
flats = models.ManyToManyField(Flat)
# logic
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile = Profile.objects.create()
profile.flats.add(flat_1)
profile.flats.add(flat_2)
profiles = Profile.objects.filter(flats__building=building)
I got in profiles 2 same profiles. How i can annotate each of them by different flat like this: profiles.first().flat == flat_1 and profiles.last().flat == flat_2?
Maybe Subquery() but how?
UPD I need this in some DRF list view. Output in JSON must be something like:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
To obtain that output, you could do:
data = Profile.objects.all().values('flats', 'id')
return Response(data=data)
in your DRF view.
You don't have to profile instances ...
I wrote the code for your exact needs at the end, but first wrote a couple of things that might be of interest.
In your code sample, you've created only one profile, I'm sure you are not getting 2 instances of Profile that are equals but only one.
The thing is if you have a QuerySet with only one entry, then:
profiles.first() == profiles.last() # True
since profile.first() and profiles.last() are the same instance.
You should try creating 2 Profile instances:
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile_1 = Profile.objects.create() # You coud/should use bulk_create here.
profile_2 = Profile.objects.create()
profile_1.flats.add(flat_1)
profile_2.flats.add(flat_2)
Then
profiles = Profile.objects.filter(flats__building=building)
will return two different profile objects.
On the other hand, obtaining the JSON like you want ...
Following the example, you posted, filter flats by profile and get the values (this also works if you have more that one profile).
Flat.objects.filter(profile=profile_1).values('profile__id', 'id')
This will return something like ("id" stands for flats ids):
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 1,
"id": 3
}
]
If you do not filter by profile (and you have more than one) you could get something like:
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 2,
"id": 3
},
{
"profile__id": 2,
"id": 4
},
...
]
Annotating to get the EXACT json you want:
Filter as shown previously annotate, and get desired values:
Flat.objects.filter(profile=profile_1).annotate(
flat_id=F('id')
).annotate(
profile_id=F('profile__id')
).values(
'profile_id', 'flat_id'
)
will give exactly what you want:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
You can do that with the right serializer and the right annotation:
The serializer:
class FlatSerializer(serializers.ModelSerializer):
class Meta:
model = Flat
fields = ('flat_id', 'building_id')
flat_id = serializers.CharField(read_only=True)
Then I would simply query Flats rather than profiles and serialize:
flats = Flat.objects \
.annotate(flat_id=F('id')) \
.filter(building=building)
serialized = FlatSerializer(flats, many=True)
print(serialized.data) # [ { flat_id: 1, building_id: 1 }, { flat_id: 2, building_id: 1 } ]
Let me know if that works for you

How do I insert or create data with Django ORM programmatically? Or how do I specific Model field name with a string?

If I have:
class Tag(models.Model):
number = models.IntegerField()
config = {
"data": 1,
"field": "number"
}
How do I do the following?
record = Tag(config["field"]=config["data"])
record.save()
You can unpack dict to arguments using this syntax **. For example:
config = {
"number": 1
}
record = Tag(**config)
record.save()
This will create new tag instance with number=1 value.

django serializer with nested column

I'm calling the following serializer -
class ResourceSerializer(serializers.ModelSerializer):
class Meta:
model = Resmst
resource_name = 'resmst'
fields = ('resmst_id', 'resmst_name', 'resmst_desc', 'resmst_limit', 'resmst_inuse', 'resmst_active', 'resmst_lstchgtm',
'resmst_prntid', 'resmst_owner', 'resmst_public', 'resmst_locked', 'resmst_offline')
read_only_fields = ('resmst_id',)
resmst_owner is a FK relation to another table. What I want to do is have the serializer display the information from a column where that FK relates.
This is the current json -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"resmst_owner": 822,
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
And this is how I want it to look -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"owner_name": "John Smith",
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
Or am I stuck with having to do it this way -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"resmst_owner": {
"owner_name": "John Smith"
},
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
Haven't tested this but it looks like this question. Try to use a SerializerMethodField.
I think you should be fine with a regular Field (note that this is readonly).
class ResourceSerializer(serializers.ModelSerializer):
owner_name = serializers.Field(source='resmst_owner.owner_name')
class Meta:
model = Resmst
resource_name = 'resmst'
fields = ('resmst_id', 'resmst_name', 'resmst_desc', 'resmst_limit',
'resmst_inuse', 'resmst_active', 'resmst_lstchgtm',
'resmst_prntid', 'resmst_owner', 'resmst_public',
'resmst_locked', 'resmst_offline', 'owner_name', )
read_only_fields = ('resmst_id',)
Don't forget to add it to Meta.fields (as above).
See the section on Core arguments:
source
The name of the attribute that will be used to populate the
field. May be a method that only takes a self argument, such as
Field(source='get_absolute_url'), or may use dotted notation to
traverse attributes, such as Field(source='user.email').
The value source='*' has a special meaning, and is used to indicate
that the entire object should be passed through to the field. This can
be useful for creating nested representations. (See the implementation
of the PaginationSerializer class for an example.)
Defaults to the name of the field.

django rest framework - get two models

I have multiple models that are associated by Foreign Keys. I can export them all separately using django rest framework on a one to one basis, and I can also export multiple ones nested. However I want to be able to essentially "concatenate" them together into a single json/xml export.
The models in the example below are joined by a one to one foreign key on jobdtl_id. I have some where it's one to many but I'm hoping I can figure that out when I know how to get a view that will link 2 separate models like I want below -
Here's an example of what I want the json to look like by hitting a single URL like
http://localhost/job/4/
{
"job": {
"-id": "9878",
"-name": "This is the job",
"-master": "blahserver",
"-dbversion": "234",
"-xmlversion": "1",
"jobmst": {
"jobmst_id": "9878",
"jobmst_type": "2",
"jobmst_prntid": "234",
"jobmst_active": "Y",
"jobmst_name": "This is the job",
"jobmst_owner": "Owner",
"jobdtl_id": "9878",
"jobmst_lstchgtm": {
"-date": "Y",
"#text": "2013-10-23 09:22:08.0"
},
"jobmst_prntname": "Parent",
"jobmst_alias": "9878"
},
"jobdtl": {
"jobdtl_id": "9878",
"jobdtl_cmd": "blah.exe",
"jobdtl_failalarm": "NULL",
"nodmst_id": "NULL",
"nodlstmst_id": "NULL",
"jobdtl_inhevent": "Y",
"jobdtl_inhoptions": "Y",
"jobdtl_inhagent": "Y",
"jobdtl_inhrepeat": "Y",
"jobdtl_inhtime": "Y",
"jobdtl_timewin": "NULL",
"jobdtl_saveoutput": "Y",
"jobdtl_outputname": "NULL",
"jobdtl_trackmethod": "1",
"jobdtl_trackcmd": "NULL",
"jobdtl_deplogic": "1",
"jobdtl_rerun": "NULL",
"jobdtl_params": "--blah --ok"
},
"jobdep": [
{
"jobdep_id": "79670",
"jobmst_id": "9878",
"jobdep_type": "1",
"jobdep_jobmst": "another job",
"varmst_id": "NULL"
},
{
"-num": "2",
"jobdep_id": "83783",
"jobmst_id": "9878",
"jobdep_type": "1",
"jobdep_jobmst": "and another",
"varmst_id": "NULL"
}
],
"trgjob": [
{
"trgjob_id": "22286",
"trgmst_id": "23455",
"jobmst_id": "9878"
},
{
"-num": "2",
"trgjob_id": "28980",
"trgmst_id": "23521",
"jobmst_id": "9878"
},
{
"-num": "3",
"trgjob_id": "28981",
"trgmst_id": "9237",
"jobmst_id": "9878"
}
]
}
}
The models are basically like this -
class Jobdtl(models.Model):
jobdtl_id = models.IntegerField(primary_key=True)
jobdtl_cmd = models.TextField(blank=True)
....
jobdtl_duration = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'jobdtl'
class Jobmst(models.Model):
jobmst_id = models.IntegerField(primary_key=True)
jobmst_type = models.SmallIntegerField()
....
jobdtl_id = models.ForeignKey('Jobdtl', db_column='jobdtl_id', related_name='mstdtl', blank=True, null=True)
def __unicode__(self):
return self.jobmst_name
class Meta:
managed = False
db_table = 'jobmst'
end caveat I'm converting the json from how the XML looks for the existing legacy app which is like so -
<?xml version="1.0"?>
<job id="9878" name="This is the job" master="blahserver" dbversion="532" xmlversion="1">
<jobmst>
<jobmst_id>9878</jobmst_id>
<jobmst_type>2</jobmst_type>
<jobmst_prntid>234</jobmst_prntid>
<jobmst_active>Y</jobmst_active>
<jobmst_name>This is the job</jobmst_name>
<jobmst_owner>Owner</jobmst_owner>
<jobdtl_id>9878</jobdtl_id>
<jobmst_lstchgtm date="Y">2013-10-23 09:22:08.0</jobmst_lstchgtm>
<jobmst_prntname>Parent</jobmst_prntname>
<jobmst_alias>9878</jobmst_alias>
</jobmst>
<jobdtl>
<jobdtl_id>9878</jobdtl_id>
<jobdtl_cmd>blah.exe</jobdtl_cmd>
<jobdtl_failalarm>NULL</jobdtl_failalarm>
<nodmst_id>NULL</nodmst_id>
<nodlstmst_id>NULL</nodlstmst_id>
<jobdtl_inhevent>Y</jobdtl_inhevent>
<jobdtl_inhoptions>Y</jobdtl_inhoptions>
<jobdtl_inhagent>Y</jobdtl_inhagent>
<jobdtl_inhrepeat>Y</jobdtl_inhrepeat>
<jobdtl_inhtime>Y</jobdtl_inhtime>
<jobdtl_timewin>NULL</jobdtl_timewin>
<jobdtl_saveoutput>Y</jobdtl_saveoutput>
<jobdtl_outputname>NULL</jobdtl_outputname>
<jobdtl_trackmethod>1</jobdtl_trackmethod>
<jobdtl_trackcmd>NULL</jobdtl_trackcmd>
<jobdtl_deplogic>1</jobdtl_deplogic>
<jobdtl_rerun>NULL</jobdtl_rerun>
<jobdtl_params>--blah --ok</jobdtl_params>
</jobdtl>
<jobdep>
<jobdep_id>79670</jobdep_id>
<jobmst_id>9878</jobmst_id>
<jobdep_type>1</jobdep_type>
<jobdep_jobmst>another job</jobdep_jobmst>
<varmst_id>NULL</varmst_id>
</jobdep>
<jobdep num="2">
<jobdep_id>83783</jobdep_id>
<jobmst_id>9878</jobmst_id>
<jobdep_type>1</jobdep_type>
<jobdep_jobmst>and another</jobdep_jobmst>
<varmst_id>NULL</varmst_id>
</jobdep>
<trgjob>
<trgjob_id>22286</trgjob_id>
<trgmst_id>23455</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
<trgjob num="2">
<trgjob_id>28980</trgjob_id>
<trgmst_id>23521</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
<trgjob num="3">
<trgjob_id>28981</trgjob_id>
<trgmst_id>9237</trgmst_id>
<jobmst_id>9878</jobmst_id>
</trgjob>
</job>
class MSTSerializer(serializers.HyperlinkedModelSerializer):
jobdtl_id = DTLSerializer()
class Meta:
model = Jobmst
fields = ('id', 'url', 'jobdtl_id'...)
class DTLSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobdtl
fields = ('id', 'url', ...)
Would result in a more correct data structure of
{
"jobmst_id": 4,
"jobmst_type": 1,
"jobdtl_id": {
"jobdtl_id": 4,
"jobdtl_cmd": null,
"jobdtl_duration": 1379
},
}
I found the solution by doing the following in my views.py
...
#csrf_exempt
def tesxml_test(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
mst = Jobmst.objects.using('database1').get(jobmst_id=pk)
dtl = Jobdtl.objects.using('database1').get(jobdtl_id=pk)
dep = Jobdep.objects.using('database2').filter(jobmst_id=pk).order_by('jobdep_id')
trg = Trgjob.objects.using('database1').filter(jobmst_id=pk).order_by('trgjob_order')
except Jobmst.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
#Get String Results of 4 queries
jobmststring = JobmstSerializer(mst)
jobdtlstring = JobdtlSerializer(dtl)
jobdepstring = JobdepSerializer(dep)
trgjobstring = TrgjobSerializer(trg)
#Get serialized Results of 4 queries
jobmst_serialized = {'jobmst': jobmststring.data}
jobdtl_serialized = {'jobdtl': jobdtlstring.data}
jobdep_serialized = {'jobdep': jobdepstring.data}
trgjob_serialized = {'trgjob': trgjobstring.data}
jobgroup = jobmst_serialized, jobdtl_serialized, jobdep_serialized, trgjob_serialized,
jobgroupresponse = TESXMLResponse(jobgroup)
return jobgroupresponse
...
It's not perfect but it puts me on the next step of my problem which is customizing the renderer to get the data in the root fields which I have another SO question for :)