django Charfield suitable for a primary key? - django-models

In my django model I would like to set the field unique_id below to be the primary key as this will be the field used in queries for the model. It satisfies unique=True and null=False. However as django sets an AutoField to primary key in the background I'm unsure whether a CharField (which uses a unique 3 character code) is suitable as a primary key or whether this will be sub-optimal?
class PaymentMethod(models.Model):
unique_id = models.CharField(max_length=3)
last_updated = models.DateTimeField(auto_now=True)

There is nothing wrong with setting the CharField to be a primary key, by changing the model to be:
class PaymentMethod(models.Model):
unique_id = models.CharField(max_length=3, primary_key=True)
last_updated = models.DateTimeField(auto_now=True)
In actual fact if the unique_id is the field you will be querying it makes perfect sense to use it. Your other options are to use your existing model but with unique=True:
class PaymentMethod(models.Model):
unique_id = models.CharField(max_length=3, unique=True)
last_updated = models.DateTimeField(auto_now=True)
In this case your primary key will be an auto incrementing integer as you previously stated.
Another option depending on the number of records you are looking to store in the PaymentMethod models; and where the data is used elsewhere within your application. Is too look into using a model choices field. The model choices field might be on your payments or order model (depends on your application and what you are trying to do). This removes the need for a foreignkey and potentially reduces the number of queries in your app.
It could look something like this:
class Payment(models.Model):
VISA = 'VIS'
CREDIT = 'CRE'
MASTER_CARD = 'MAS'
PAYPAL = 'PAL'
PAYMENT_OPTIONS= (
(VISA, 'Visa'),
(CREDIT, 'Credit Card'),
(MASTER_CARD, 'Master Card'),
(PAYPAL, 'Paypal')
)
items = models.ForeignKey(Item)
date = models.DateField(auto_now=True)
...
payment_method = models.CharField(max_length=3, choices=PAYMENT_OPTIONS, default=VISA)
The PAYMENT_OPTIONS can be used to render dropdown boxes on the forms when using django model forms. Otherwise the users selection is limited to the options listed within this model.
This method would be a lot more efficient if you only have a small subset of PaymentMethod(s).

It's possible to use CharField as primary key. You just have to mark the field as primary key.
field_name = models.CharField(primary_key=True, max_length=100)
But I wouldn't recommend it because:
Primary keys are used in urls (typically in Rest APIs) - but not all characters are allowed in urls
DRF (django-rest-framework) use urls patterns that don't catch some characters by default (for example ".")
Primary keys must be unique - it's harder to accomplish it if the field is a string, especially when you let users to define it

Related

SQLAlchemy: foreignKeys from multiple Tables (Many-to-Many)

I'm using flask-sqlalchemy orm in my flask app which is about smarthome sensors and actors (for the sake of simplicity let's call them Nodes.
Now I want to store an Event which is bound to Nodes in order to check their state and other or same Nodes which should be set with a given value if the state of the first ones have reached a threshold.
Additionally the states could be checked or set from/for Groups or Scenes. So I have three diffrent foreignkeys to check and another three to set. All of them could be more than one per type and multiple types per Event.
Here is an example code with the db.Models and pseudocode what I expect to get stored in an Event:
db = SQLAlchemy()
class Node(db.Model):
id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String(20))
# columns snipped out
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String(20))
# columns snipped out
class Scene(db.Model):
id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String(20))
# columns snipped out
class Event(db.Model):
id = db.Column(db.Integer, primary_key=True)
# The following columns may be in a intermediate table
# but I have no clue how to design that under these conditions
constraints = # list of foreignkeys from diffrent tables (Node/Group/Scene)
# with threshold per key
target = # list of foreignkeys from diffrent tables (Node/Group/Scene)
# with target values per key
In the end I want to be able to check if any of my Events are true to set the bound Node/Group/Scene accordingly.
It may be a database design problem (and not sqlalchemy) but I want to make use of the advantages of sqla orm here.
Inspired by this and that answer I tried to dig deeper, but other questions on SO were about more specific problems or one-to-many relationships.
Any hints or design tips are much appreciated. Thanks!
I ended up with a trade-off between usage and lines of code. My first thought here was to save as much code as I can (DRY) and defining as less tables as possible.
As SQLAlchemy itself points out in one of their examples the "generic foreign key" is just supported because it was often requested, not because it is a good solution. With that less db functionallaty is used and instead the application has to take care about key constraints.
On the other hand they said, having more tables in your database does not affected db performance.
So I tried some approaches and find a good one that fits to my usecase. Instead of a "normal" intermediate table for many-to-many relationships I use another SQLAlchemy class which has two one-to-many relations on both sides to connect two tables.
class Event(db.Model):
id = db.Column(db.Integer, primary_key=True)
noodles = db.relationship('NoodleEvent', back_populates='events')
# columns snipped out
def get_as_dict(self):
return {
"id": self.id,
"nodes": [n.get_as_dict() for n in self.nodes]
}
class Node(db.Model):
id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String(20))
events = db.relationship('NodeEvent', back_populates='node')
# columns snipped out
class NodeEvent(db.Model):
ev_id = db.Column('ev_id', db.Integer, db.ForeignKey('event.id'), primary_key=True)
n_id = db.Column('n_id', db.Integer, db.ForeignKey('node.id'), primary_key=True)
value = db.Column('value', db.String(200), nullable=False)
compare = db.Column('compare', db.String(20), nullable=True)
node = db.relationship('Node', back_populates="events")
events = db.relationship('Event', back_populates="nodes")
def get_as_dict(self):
return {
"trigger_value": self.value,
"actual_value": self.node.status,
"compare": self.compare
}
The trade-off is that I have to define a new class everytime I bind a new table on that relationship. But with the "generic foreign key" approach I also would have to check from where the ForeignKey is comming from. Same work in the end of the day.
With my get_as_dict() function I have a very handy access to the related data.

Convert three modelsto one single query django query

This are my model with some of the fields:
class Advertisers(models.Model):
account_manager_id = models.ForeignKey(AccountManagers, on_delete=models.CASCADE,null=True, db_column='account_manager_id',related_name="advertisers")
class AdvertiserUsers(models.Model):
user_id = models.OneToOneField('Users', on_delete=models.CASCADE,null=True,db_column='user_id', related_name='advertiser_users')
advertiser_id = models.ForeignKey('Advertisers', on_delete=models.CASCADE,null=True,db_column='advertiser_id', related_name='advertiser_users')
class Users(models.Model):
email = models.CharField(unique=True, max_length=100)
I want Id's, user ids and email of all advertisers.
Id's of all user:-
advertiser_ids = advertisers.objects.all() # can get id from here
find user_ids of advertiser_ids:
user_ids = AdvertiserUsers.objects.filter(advertiser_id__in=advertiser_ids) # can get user_id from here
find id and email using this query:
user_ids = Users.objects.filter(id__in=user_ids) # can get email from here
How to make it shorter like directly querying from Advertisers i will be able to get Users models email.
Thankyou in advance
You can filter with:
Users.objects.filter(advertiser_users__advertiser_id__isnull=False).distinct()
The .distinct() [Django-doc] will prevent returning the same Users multiple times.
You can annotate the User objects with the Advertisers primary key, etc:
from django.db.models import F
Users.objects.filter(advertiser_users__advertiser_id__isnull=False).annotate(
account_manager_id=F('advertiser_users__advertiser_id__account_manager_id'),
advertiser_id=F('advertiser_users__advertiser_id')
)
The Users objects that arise from this have a .email attribute (and the other attributes that belong to a Users object), together with a .account_manager_id and an .advertiser_id. That being said, this is probably not a good idea: the way you have modeled this right now, is that a Users object can relate to multiple Advertisers objects, so it makes not much sense to add these together.
You can for each user access the related Advertisers with:
myusers = Users.objects.filter(
advertiser_users__advertiser_id__isnull=False
).prefetch_related(
'advertiser_users',
'advertiser_users__advertiser_id'
).distinct()
for user in myusers:
print(f'{user.email}')
for advuser in user.advertiser_users.all():
print(f' {advuser.advertiser_user.pk}')
Note: normally a Django model is given a singular name, so User instead of Users.
Note: Normally one does not add a suffix _id to a ForeignKey field, since Django
will automatically add a "twin" field with an _id suffix. Therefore it should
be account_manager_id, instead of account_manager.
Advertisers.objects.all().values_list('id','account_manager_id','advertiser_users__user_id',advertiser_users__user_id__email)

which particular field of the model is referred under the foreign key in django's orm?

i have two classes 'topics' and 'webpage' and i'm trying to assign webpage.category as a foreign key referencing to topics.top_name.
But unlike raw sql where a foreign key can reference to a particular field in another table, in django's orm we just provide the referenced class'(table's) name and not the particular field the FK is referrring to.
class Topic(models.Model):
top_name = models.CharField(max_length=264, unique=True)
top_author = models.CharField(max_length=264)
class Webpage(models.Model):
category = models.ForeignKey(Topic)
name = models.CharField(max_length=264)
url = models.URLField()
You can set the to_field=… [Django-doc] in the ForeignKey constructor:
class Topic(models.Model):
top_name = models.CharField(max_length=264, unique=True)
top_author = models.CharField(max_length=264)
class Webpage(models.Model):
category = models.ForeignKey(Topic, to_field='top_name', on_delete=models.CASCADE)
name = models.CharField(max_length=264)
url = models.URLField()
As specified in the documentation, the field to which you refer should be unique (which of course makes sense, since otherwise, it would be ambiguous).
Note that usually, the collation [mysql-doc] of the referencing column and the target column should be the same. Otherwise it is not completely clear when the two fields are equal.
If you do not specify the to_field, it will use the primary key of the target model.

auto increment field in Peewee

Is there a way to define autoincrement Field in peewee.
I understand we could define sequence but the need to create the sequence manually and not managed by create_tables deters me from using it. ( The build process is managed by create tables and I would prefer not to add manual steps )
import peewee
class TestModel(peewee.Model):
test_id = peewee.BigIntegerField(sequence='test_id_seq')
Alternate to the above code I would rather have. As most databases have serial field I dont see a point maintaining a sequence.
import peewee
class TestModel(peewee.Model):
test_id = peewee.AutoIncremenetIntField()
Either you can use PrimaryKeyField() as #wyatt mentioned in comment
or you can use Playhouse- Signal Support (peewee extensions)
from playhouse.signals import Model, pre_save
class MyModel(Model):
data = IntegerField()
#pre_save(sender=MyModel)
def on_save_handler(model_class, instance, created):
# find max value of temp_id in model
# increment it by one and assign it to model instance object
next_value = MyModel.select(fn.Max(MyModel.temp_id))[0].temp_id +1
instance.temp_id = next_value
The given answers here are outdated but this was still my first Google search result.
Peewee has a special field type for an auto incrementing primary key called AutoField:
The AutoField is used to identify an auto-incrementing integer primary
key. If you do not specify a primary key, Peewee will automatically
create an auto-incrementing primary key named “id”.
Take a look at the documentation. Example usage:
class Event(Model):
event_id = AutoField() # Event.event_id will be auto-incrementing PK.

Django: overriding default field names in ManyToManyField

Good day,
Using a ManyToManyField as in the example below causes django to automatically create a Model called country_region with fields country_id and region_id, which is very cool. But what if the region_country database table (for reasons beyond the scope of this thread) has field names other than country_id and region_id?
Django generates the field names in the many-to-many table based on the model names of the related tables, so the only way I have found to achieve this is to change the two Model names. But that forces me to have model names that I don't want! How can I tell django what field names to use in the automatic many-to-many Model?
I have tried explicitly specifying the many-to-many Model using the through= keyword argument of the ManyToManyField, and this works, but then forms based on the Region Model do not allow saving...
Any advice greatly appreciated.
Thanks,
Randal
class Country(models.Model):
country_id = models.AutoField(primary_key=True)
country_name = models.CharField(max_length=200)
class Meta:
managed = False
db_table = 'country'
def __unicode__(self):
return '%s' % (self.country_name)
class Region(models.Model):
region_id = models.AutoField(primary_key=True)
region_name = models.CharField(max_length=200)
region = models.ManyToManyField(Country, db_table='region_country')
class Meta:
managed = False
db_table = 'region'

Resources