InvalidCursorName : Django Admin error referencing wrong column in ForeignKey - python

I've setup a relationship using django's ForeignKey against 2 unmanaged tables like so:
class Product(BaseModel):
publish_name = models.CharField(unique=True, max_length=40)
# this works:
associated_country = models.ForeignKey('geonames.Countryinfo', models.DO_NOTHING, db_column='published_country', blank=True, null=True)
# this doesn't:
associated_continent = models.ForeignKey('geonames.Continentcodes', on_delete=models.DO_NOTHING, db_column='published_continent' blank=True, null=True)
class Meta:
managed = True
db_table = 'product'
class Continentcodes(models.Model):
code = models.CharField(max_length=2, primary_key=True, unique=True)
name = models.TextField(blank=True, null=True)
geoname_id = models.ForeignKey('Geoname', models.DO_NOTHING, blank=True, null=True, unique=True)
class Meta:
managed = False
db_table = 'geoname_continentcodes'
class Countryinfo(models.Model):
iso_alpha2 = models.CharField(primary_key=True, max_length=2)
country = models.TextField(blank=True, null=True)
geoname = models.ForeignKey('Geoname', models.DO_NOTHING, blank=True, null=True)
neighbours = models.TextField(blank=True, null=True)
class Meta:
ordering = ['country']
managed = False
db_table = 'geoname_countryinfo'
verbose_name_plural = 'Countries'
When I go to edit an entry in the django admin page for 'Products' I see this:
InvalidCursorName at /admin/product/6/change/ cursor
"_django_curs_140162796078848_sync_5" does not exist
The above exception (column geoname_continentcodes.geoname_id_id does
not exist LINE 1: ...ntcodes"."code", "geoname_continentcodes"."name",
"geoname_c...
^ HINT: Perhaps you meant to reference the column
"geoname_continentcodes.geoname_id"
It looks like it's trying to reference geoname_continentcodes.geoname_id_id for some reason. I have tried adding to='code' in the ForeignKey relationship, but it doesn't seem to effect anything.
Additionally, the associated_country relationship seems to work just fine if I comment out the associated_continent field. The associated_continent is the column that is giving some problem.
Here is some more context about what the table looks like in the database:

Removing the '_id' as the suffix is the fix. In this case geoname_id changed to geoname fixes this. Why? I have no idea. Django is doing something behind the scenes that is not clear to me. Here is the updated model:
class Continentcodes(models.Model):
code = models.CharField(max_length=2, primary_key=True, unique=True)
name = models.TextField(blank=True, null=True)
# remove '_id' as the suffix here
geoname = models.ForeignKey('Geoname', models.DO_NOTHING, blank=True, null=True, unique=True)
class Meta:
managed = False
db_table = 'geoname_continentcodes'

Django adds _id to the end to differentiate between the object and the id column in the table, geoname_id will return the table id where as geoname will return the object.

Related

How to get first N rows from every category in Django

I have following models, with many to many table, for which I would like to get first 20 news from every category in single response.
class Category(models.Model):
code = models.CharField(primary_key=True, max_length=45)
name = models.CharField(max_length=200, blank=True, null=True)
is_active = models.TextField(blank=True, null=True) # This field type is a guess.
class Meta:
managed = False
db_table = 'category'
verbose_name_plural = 'Categories'
class News(models.Model):
source_code = models.CharField(max_length=45, blank=True, null=True)
title = models.CharField(max_length=1000, blank=True, null=True)
image = models.CharField(max_length=2000, blank=True, null=True)
link = models.CharField(max_length=1000, blank=True, null=True)
published_at = models.DateTimeField(blank=True, null=True)
scraped_at = models.DateTimeField(blank=True, null=True)
is_active = models.TextField(blank=True, null=True) # This field type is a guess.
categories = models.ManyToManyField('Category', through='NewsCategory')
class Meta:
managed = False
db_table = 'news'
verbose_name_plural = 'News'
class NewsCategory(models.Model):
news_id = models.ForeignKey(News, db_column='news_id', on_delete=models.CASCADE)
category_code = models.ForeignKey(Category, db_column='category_code', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'news_category'
unique_together = (('news_id', 'category_code'),)
verbose_name_plural = 'NewsCategory'
My view class looks like this, and here I would like to add some logic to return 20 rows for each category, for example if I have 5 categories it should return 100 news in single request.
class NewsViewSet(viewsets.ModelViewSet):
http_method_names = ['get']
serializer_class = NewsSerializer
def get_queryset(self):
queryset = News.objects.all().order_by('-published_at')
sources = self.request.query_params.getlist('sources')
if len(sources) > 0:
queryset = queryset.filter(source_code__in=sources)
return queryset
The typical way to do this is to use a window function. Django has support for them but I don't think they allow filtering on the output of them. I think this is further complicated by the m2m field. Given it's not too complex and doesn't seem to involve user input, you might just want to use a raw query.
Here's what it might look like:
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY c.code ORDER BY n.published_at DESC) AS row_num
FROM appname_news n
JOIN appname_newscategory nc
ON n.id = c.news_id
JOIN appname_category c
ON nc.category_code = c.code
) sub
WHERE
row_num <= 20
And see here for Django's guide on how to actually implement this in a view:
https://docs.djangoproject.com/en/3.2/topics/db/sql/#executing-custom-sql-directly

Django Rest API ManyToMany gives nothing [] in API

at the moment I try to get recipes from my API. I have a Database with two tables one is with recipes and their ids but without the ingredients, the other table contains the ingredients and also the recipe id. Now I cant find a way that the API "combines" those. Maybe its because I added in my ingredient model to the recipe id the related name, but I had to do this because otherwise, this error occurred:
ERRORS:
recipes.Ingredients.recipeid: (fields.E303) Reverse query name for 'Ingredients.recipeid' clashes with field name 'Recipe.ingredients'.
HINT: Rename field 'Recipe.ingredients', or add/change a related_name argument to the definition for field 'Ingredients.recipeid'.
Models
from django.db import models
class Ingredients(models.Model):
ingredientid = models.AutoField(db_column='IngredientID', primary_key=True, blank=True)
recipeid = models.ForeignKey('Recipe', models.DO_NOTHING, db_column='recipeid', blank=True, null=True, related_name='+')
amount = models.CharField(blank=True, null=True, max_length=100)
unit = models.CharField(blank=True, null=True, max_length=100)
unit2 = models.CharField(blank=True, null=True, max_length=100)
ingredient = models.CharField(db_column='Ingredient', blank=True, null=True, max_length=255)
class Meta:
managed = True
db_table = 'Ingredients'
class Recipe(models.Model):
recipeid = models.AutoField(db_column='RecipeID', primary_key=True, blank=True) # Field name made lowercase.
title = models.CharField(db_column='Title', blank=True, null=True, max_length=255) # Field name made lowercase.
preperation = models.TextField(db_column='Preperation', blank=True, null=True) # Field name made lowercase.
images = models.CharField(db_column='Images', blank=True, null=True, max_length=255) # Field name made lowercase.
#ingredients = models.ManyToManyField(Ingredients)
ingredients = models.ManyToManyField(Ingredients, related_name='recipes')
class Meta:
managed = True
db_table = 'Recipes'
When there is no issue it has to be in the serializer or in the view.
Serializer
class IngredientsSerializer(serializers.ModelSerializer):
# ingredients = serializers.CharField(source='ingredients__ingredients')
class Meta:
model = Ingredients
fields = ['ingredient','recipeid']
class FullRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientsSerializer(many=True)
class Meta:
model = Recipe
fields = ['title','ingredients']
View
class FullRecipesView(generics.ListCreateAPIView):
serializer_class = FullRecipeSerializer
permission_classes = [
permissions.AllowAny
]
queryset = Recipe.objects.all()
This is at the moment my output
But I want e.g. the recipe with id 0 and all the ingredients which have also recipe id 0.
I really hope that you can help me. Thank you so much!
From the doc of ForeignKey.related_name,
If you’d prefer Django not to create a backwards relation, set related_name to '+' or end it with '+'.
So, change the related_name of Ingredients.recipeid field to
class Ingredients(models.Model):
# rest of the fields
recipeid = models.ForeignKey(
'Recipe',
models.DO_NOTHING,
db_column='recipeid',
blank=True,
null=True,
related_name="ingredients_ref" # Changed the related name
)
Then, migrate the database using python manage.py makemigrations and python manage.py migrate
Then, update your FullRecipeSerializer class as,
class FullRecipeSerializer(serializers.ModelSerializer):
ingredients_forward = IngredientsSerializer(many=True, source="ingredients")
ingredients_backward = IngredientsSerializer(many=True, source="ingredients_ref")
class Meta:
model = Recipe
fields = ['title', 'ingredients_forward', 'ingredients_backward']
Note that, here I have added two fields named ingredients_forward and ingredients_backward because there existing two types of relationships between Recipe and Ingredients and I am not sure which one you are seeking.

Django multiple foreign key to a same table

I need to log the transaction of the item movement in a warehouse. I've 3 tables as shown in the below image. However Django response error:
ERRORS:
chemstore.ItemTransaction: (models.E007) Field 'outbin' has column name 'bin_code_id' that is used by another field.
which is complaining of multiple uses of the same foreign key. Is my table design problem? or is it not allowed under Django? How can I achieve this under Django? thankyou
DB design
[Models]
class BinLocation(models.Model):
bin_code = models.CharField(max_length=10, unique=True)
desc = models.CharField(max_length=50)
def __str__(self):
return f"{self.bin_code}"
class Meta:
indexes = [models.Index(fields=['bin_code'])]
class ItemMaster(models.Model):
item_code = models.CharField(max_length=20, unique=True)
desc = models.CharField(max_length=50)
long_desc = models.CharField(max_length=150, blank=True)
helper_qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
def __str__(self):
return f"{self.item_code}"
class Meta:
verbose_name = "Item"
verbose_name_plural = "Items"
indexes = [models.Index(fields=['item_code'])]
class ItemTransaction(models.Model):
trace_code = models.CharField(max_length=20, unique=False)
item_code = models.ForeignKey(
ItemMaster, related_name='trans', on_delete=models.CASCADE, null=False)
datetime = models.DateTimeField(auto_now=False, auto_now_add=False)
qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
action = models.CharField(
max_length=1, choices=ACTION, blank=False, null=False)
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
remarks = models.TextField(blank=True)
def __str__(self):
return f"{self.trace_code} {self.datetime} {self.item_code} {dict(ACTION)[self.action]} {self.qty} {self.unit} {self.in_bin} {self.out_bin}"
you have same db_column in two fields so change it
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='other_bin_code', on_delete=models.CASCADE, null=False) /*change db_column whatever you want but it should be unique*/
If are linked to the same model name, You should use different related_name for each foreign_key filed . here is the exemple :
address1 = models.ForeignKey(Address, verbose_name=_("Address1"),related_name="Address1", null=True, blank=True,on_delete=models.SET_NULL)
address2 = models.ForeignKey(Address, verbose_name=_("Address2"),related_name="Address2", null=True, blank=True,on_delete=models.SET_NULL)
thank you for everyone helped. According to Aleksei and Tabaane, it is my DB design issue (broken the RDBMS rule) rather than Django issue. I searched online and find something similar: ONE-TO-MANY DB design pattern
In my case, I should store in bin and out bin as separated transaction instead of both in and out in a single transaction. This is my solution. thankyou.
p.s. alternative solution: I keep in bin and out bin as single transaction, but I don't use foreign key for bins, query both in bin and out bin for the bin selection by client application.

I've got error message : TypeError: unsupported operand type(s) for %: 'DeferredAttribute' and 'dict' in django

I created new model in my django rest freamework app and I've got this error message :
TypeError: unsupported operand type(s) for %: 'DeferredAttribute' and 'dict'
here is the error screenshot:
I can't understand what's the problem?
Here is my models.py:
from django.db import models
class TblUserAccounts(models.Model):
uid = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=20)
alias_username = models.CharField(unique=True, max_length=20, blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_user_accounts'
ordering = ['uid']
class TblUserDetails(models.Model):
detail_id = models.IntegerField(primary_key=True)
useraccount = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name=TblUserAccounts.uid)
first_name = models.CharField(max_length=25, blank=True, null=True)
last_name = models.CharField(max_length=45, blank=True, null=True)
birthdate = models.DateTimeField(blank=True, null=True)
record_time = models.DateTimeField()
creator = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name=TblUserAccounts.uid, db_column='creator')
class Meta:
managed = False
db_table = 'tbl_user_details'
ordering = ['record_time']
class TblUserPassword(models.Model):
id_password = models.AutoField(primary_key=True)
useraccount_id_pwd = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name=TblUserAccounts.uid, db_column='useraccount_id_pwd')
salt = models.CharField(max_length=200, blank=True, null=True)
hash = models.CharField(max_length=200, blank=True, null=True)
record_time = models.DateTimeField()
creator = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name=TblUserAccounts.uid, db_column='creator')
class Meta:
managed = False
db_table = 'tbl_user_password'
ordering = ['record_time']
Of course you should know that I'm new to Python programming.
I guess that the problem is in TblUserDetails model but I don't know what is it?
Thanks a lot for your attentions.
Without being able to test it I can really only throw a shot in the dark. Doing some reading though, I think the issue is what you use for related_name. Try the following:
class TblUserDetails(models.Model):
detail_id = models.IntegerField(primary_key=True)
useraccount = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name='TblUserAccounts.uid')
first_name = models.CharField(max_length=25, blank=True, null=True)
last_name = models.CharField(max_length=45, blank=True, null=True)
birthdate = models.DateTimeField(blank=True, null=True)
record_time = models.DateTimeField()
creator = models.ForeignKey(TblUserAccounts, models.DO_NOTHING, related_name='TblUserAccounts.uid', db_column='creator')
class Meta:
managed = False
db_table = 'tbl_user_details'
ordering = ['record_time']
Note you will need to do this fix for TblUserPassword as well if this is the issue.
I think related_name shouldn't point to the attribute of another class but it is just a name field:
ForeignKey.related_name
The name to use for the relation from
the related object back to this one. It’s also the default value for
related_query_name (the name to use for the reverse filter name from
the target model). See the related objects documentation for a full
explanation and example. Note that you must set this value when
defining relations on abstract models; and when you do so some special
syntax is available.
If you’d prefer Django not to create a backwards relation, set
related_name to '+' or end it with '+'. For example, this will ensure
that the User model won’t have a backwards relation to this model.
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='+', )
Let me know if this isn't the issue and I will see if I can find something else.

Django Tastypie ToOneField error

I'm trying to create a one-to-one relationship matching a table with property billing records to another with physical addresses, but I keep getting this error. When I search for the error, nothing shows that is relevant to this situation.
I've no idea what "[<PropertyRecord: 242811400004>]" is referencing since it isn't a PIN number and doesn't exist in any tables.
Also, I've been through the data and there are no null values on the pin in either table.
Getting this error:
{
error: "The object '[<PropertyRecord: 242811400004>]' has an empty attribute 'pin' and doesn't allow a default or null value."
}
Models:
class PropertyRecord(models.Model):
pin = models.BigIntegerField(db_index=True, max_length=20)
date_added = models.DateField(null=True)
last_chgdte = models.DateField()
name = models.CharField(db_index=True, max_length=30, null=True)
address1 = models.CharField(max_length=100, null=True)
address2 = models.CharField(max_length=100, null=True)
city = models.CharField(max_length=50, null=True)
state = models.CharField(max_length=2, null=True)
zip = models.CharField(max_length=10, null=True)
class Meta:
unique_together = ("pin", "last_chgdte")
select_on_save = True
def __str__(self):
return str(self.pin)
class PropertyAddress(models.Model):
pin = models.BigIntegerField(db_index=True, max_length=20, unique=True)
street_address = models.CharField(max_length=50, null=True)
city_name = models.CharField(max_length=50, null=True)
zip_code = models.IntegerField(max_length=5, null=True)
class Meta:
select_on_save = True
def __str__(self):
return str(self.pin)
Resources:
class PropertyAddressResource(ModelResource):
class Meta:
queryset = PropertyAddress.objects.all()
class PropertyRecordResource(ModelResource):
full_address = fields.ToOneField(
PropertyAddressResource,
attribute=lambda bundle: PropertyRecord.objects.filter(pin=bundle.obj.pin),
full=True,
null=True
)
class Meta:
queryset = PropertyRecord.objects.all()
resource_name = 'propertyrecords'
I can't imagine that joining tables wouldn't be a common need, so it should have a simple solution.
Rather, I was able to get around the issue by using dehydrate, which feels overly complicated but works. +1 to this being a PITA with the extra step to serialize the data.
def dehydrate(self, bundle):
query_data = PropertyAddress.objects.filter(pin=bundle.obj.pin)
results = serialize('json', query_data, fields=('street_address', 'city_name', 'zip_code'))
bundle.data['full_address'] = json.loads(results)
return bundle

Categories

Resources