Using django-fsm to determine the state of an object - django

How do I get the current state of a database item using django-fsm.
I have tried get_state() but it returns a null value.
Here is my code:
from django.db import models
from django_fsm import FSMField, transition
STATES = ("Open", "In Progress", "Re Opened", "Done", "Closed")
STATES = list(zip(STATES, STATES))
class Ticket(models.Model):
title = models.CharField(max_length=40)
state = FSMField(default=STATES[0], choices=STATES)
Is there a way of getting the state field using django-fsm library.
Also, how do I get the available state transitions using the model methods.

You can get the value of the stat field by accessing it like a normal field:
ticket.state
If you want to get the display friendly version, FSMField works like any CharField(choices=[]) field using:
ticket.get_state_display()
You can get all of the available transitions by calling:
ticket.get_available_state_transitions()
You didn't define any transitions on your model so this call would not return anything at the moment.

Related

Child model updates Parent model in Django ForeignKey relationship

Assuming the following models schema,
Parent model:
class Batch(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
One of many child models:
class Data(models.Model):
batch = models.ForeignKey(Batch, on_delete=models.ON_CASCADE)
timestamp = models.DateTimeField()
My goals is the following: to have a start field of parent model that is always updated when any child model is modified.
Basically, if the timestamp of a newly data instance is older than the start field I want the start field to be updated to that instance timestamp value. In the case of deletion of the data instance which is the oldest time reference point I want batch start field to be updated to the second oldest. Vice-versa for the end field.
One of the possible way to do this is to add post or pre-save signal of relative models and Update your necessary fields according to this. Django official documentation for signal, link. I want to add another link, one of the best blog post i have seen regarding django signal.
Edit for André Guerra response
One of easiest way to do a get call and bring Batch instance. What i want to say
#receiver(post_save,sender=Data)
def on_batch_child_saving(sender,instance,**kwargs):
batch_instance = Batch.objects.get(pk=instance.batch)
if (instance.timestamp < batch_instance.start):
batch_instance.start = instance.timestamp
batch_instance.save()
elif (instance.timestamp > batch_instance.end):
batch_instance.end = instance.timestamp
batch_instance.save()
Based on Shakil suggestion, I come up with this: (my doubt here was on how to save the parent model)
#receiver(post_save,sender=Data)
def on_batch_child_saving(sender,instance,**kwargs):
if (instance.timestamp < instance.batch.start):
instance.batch.start = instance.timestamp
instance.batch.save()
elif (instance.timestamp > instance.batch.end):
instance.batch.end = instance.timestamp
instance.batch.save()

flask-sqlalchemy - change lazy in different situations

Take the example in document
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
addresses = db.relationship('Address', lazy='dynamic',
backref=db.backref('person', lazy='select'))
We can only set the lazy parameter to relationship when we build the models. But I found no document show how to change it after instantiated it.
In most of my situation like
user = User.query.first()
addresses = user.addresses.filter(is_del=0).all()
which is One-to-Many or
wallet = user.wallet
which is One-to-One Model, I just set the lazy to dynamic or select to let the model only get data what I need.
However recently I want to export the data in database using Admin Front-end page.
user_list = UserModel.query.all()
for x in user_list:
item = {
"ID": x.id if x.id else ''
}
if 'basic' in fields or is_all_field == 1:
ex_item = {
"Email": x.base.account,
"Full Name": x.base.full_name,
"Display Name": x.display_name if x.display_name else '',
"Phone Number": x.base.phone_number if x.base.phone_number else '',
"Status": status_map(x.is_sharer,x.is_block,x.status_id)
"Register Time": x.create_date.strftime('%Y-%m-%d %H:%M:%S') if x.create_date else ''
}
item = {**item, **ex_item}
if ......
........
If I continue to use select and dynamic as lazy. It will be very very slow cause every loop the parent query will access database every time when it use subquery.
I test the speed between select and joined using single field like "Full Name": x.base.full_name to export all user data. Select got 53s and joined got 0.02s.
Is there a way to change the lazy parameter based on not changing the original Model?
According to the documentation you can use options to define the type of loading you want. I believe it will override your default value defined in your relationship
Useful links
Joined Loads
Select Loads
Lazy Load
So, basically, if you are using lazy=' select', lazy load, and want to switch to joinedload to optimize your query, use the following:
from sqlalchemy.orm import joinedload # import the joinedload
user_list = db.session.query(UserModel).options(joinedload("person")) # running a joinedload which will give you access to all attributes in parent and child class
for x in user_list:
# now use x.account directly

Django - counting objects in database inside model declaraton

I have model with field named "number". It's not the same as id, it's position while displayed on website, users can sort teams around.
I need something for default property of number. I think I should just count the number of positions in the database and add one, so if there are 15 positions inside db, the new team would be last on list and have "number" 16.
But I don't know how to do it, inside models.py file.
Inside views.py I would just use
Iteam.objects.count()
But what can I use inside model declaration? How can the model check itself?
Edit:
I tried do it as Joey Wilhelm suggested:
from django.db import models
class ItemManager(models.Manager):
def next_number(self):
return self.count() + 1
class Iteam(models.Model):
name = models.CharField()
objects = ItemManager()
number = models.IntegerField(default=objects.next_number())
Unfortunetly i get error:
AttributeError: 'NoneType' object has no attribute '_meta'
This is actually something you would want to do on a manager, rather than the model itself. The model should represent, and perform actions for, an individual instance. Whereas the manager represents, and performs actions for a collection of instances. So in this case you might want something like:
from django.db import models
class ItemManager(models.Manager):
def next_number(self):
return self.count() + 1
class Item(models.Model):
number = models.IntegerField()
objects = ItemManager()
That being said, I could see this leading to a lot of data integrity issues. Imagine the scenario:
4 items are created, with numbers 1, 2, 3, 4
Item 2 gets deleted
1 new item is created; this item now has a number of 4, giving you a duplicate
Edit:
The above approach will work only for pre-generating 'number', such as:
Iteam.objects.create(number=Iteam.objects.get_number(), name='foo')
In order to have it work as an actual default value for the field, you would need to use a less savory approach, such as:
from django.db import models
class Iteam(models.Model):
name = models.CharField()
number = models.IntegerField(default=lambda: Iteam.get_next_number())
#classmethod
def get_next_number(cls):
return cls.objects.count() + 1
I would still warn against this approach, however, as it still could lead to the data integrity issues mentioned above.

Django - join two models

For the following models I want to retrieve all the devices that have an entry in the History table with transition_date between a specified interval:
class History(models.Model):
device = models.ForeignKey(DeviceModel, to_field='id')
transition_date = models.DateTimeField()
class Meta:
db_table = 'History'
class DeviceModel(models.Model):
id = models.IntegerField()
name = models.CharField()
class Meta:
db_table = 'Devices'
I have this code that filters for the specified interval:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
That gives me as many rows as History table has with transition_date in the specified range.
The filter function performs an INNER JOIN between DeviceModel and History on device id retrieving only DeviceModel fields. My question is how do I retrieve data from both History and DeviceModel at the same time while joining them as with filter/select_related on device id.
I'd rather not write a custom SQL query.
In your models Device and History models are related with a foreign key from History to DeviceModel, this mean when you have a History object you can retrieve the Device model related to it, and viceversa (if you have a Device you can get its History).
Example:
first_history = History.objects.all()[0]
first_history.device # This return the device object related with first_history
first_history.device.name # This return the name of the device related with first_history
But it works also in the other way, you could do:
first_device = Device.objects.all()[0]
first_device.history # This return the history object related with device
first_device.history.transition_date # Exactly as before, can access history fields
So in your query:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
This return a device list, but you can access to the history related with each device object
Isn't that enough for you ? You have a Device list, and each device can access to its related History object
Info: When you declare a ForeignKey field the models are related by id for default, I say this because you're doing:
device = models.ForeignKey(DeviceModel, to_field='id')
as you can see you're using to_field='id' but this relation is done by default, if you do:
device = models.ForeignKey(DeviceModel)
You'll get same results
(EDIT) Using .values() to obtain list [device.name, history.date]
To get a list like you said [device.name, history.date] you can use .values() function of Django QuerySet, official documentation here
You can try something like:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate)).values('name','history__transition_date')
# Notice that it is 'history _ _ transition_date with 2 underscores

How can i get a list of objects from a postgresql view table to display

this is a model of the view table.
class QryDescChar(models.Model):
iid_id = models.IntegerField()
cid_id = models.IntegerField()
cs = models.CharField(max_length=10)
cid = models.IntegerField()
charname = models.CharField(max_length=50)
class Meta:
db_table = u'qry_desc_char'
this is the SQL i use to create the table
CREATE VIEW qry_desc_char as
SELECT
tbl_desc.iid_id,
tbl_desc.cid_id,
tbl_desc.cs,
tbl_char.cid,
tbl_char.charname
FROM tbl_desC,tbl_char
WHERE tbl_desc.cid_id = tbl_char.cid;
i dont know if i need a function in models or views or both. i want to get a list of objects from that database to display it. This might be easy but im new at Django and python so i having some problems
Django 1.1 brought in a new feature that you might find useful. You should be able to do something like:
class QryDescChar(models.Model):
iid_id = models.IntegerField()
cid_id = models.IntegerField()
cs = models.CharField(max_length=10)
cid = models.IntegerField()
charname = models.CharField(max_length=50)
class Meta:
db_table = u'qry_desc_char'
managed = False
The documentation for the managed Meta class option is here. A relevant quote:
If False, no database table creation
or deletion operations will be
performed for this model. This is
useful if the model represents an
existing table or a database view that
has been created by some other means.
This is the only difference when
managed is False. All other aspects of
model handling are exactly the same as
normal.
Once that is done, you should be able to use your model normally. To get a list of objects you'd do something like:
qry_desc_char_list = QryDescChar.objects.all()
To actually get the list into your template you might want to look at generic views, specifically the object_list view.
If your RDBMS lets you create writable views and the view you create has the exact structure than the table Django would create I guess that should work directly.
(This is an old question, but is an area that still trips people up and is still highly relevant to anyone using Django with a pre-existing, normalized schema.)
In your SELECT statement you will need to add a numeric "id" because Django expects one, even on an unmanaged model. You can use the row_number() window function to accomplish this if there isn't a guaranteed unique integer value on the row somewhere (and with views this is often the case).
In this case I'm using an ORDER BY clause with the window function, but you can do anything that's valid, and while you're at it you may as well use a clause that's useful to you in some way. Just make sure you do not try to use Django ORM dot references to relations because they look for the "id" column by default, and yours are fake.
Additionally I would consider renaming my output columns to something more meaningful if you're going to use it within an object. With those changes in place the query would look more like (of course, substitute your own terms for the "AS" clauses):
CREATE VIEW qry_desc_char as
SELECT
row_number() OVER (ORDER BY tbl_char.cid) AS id,
tbl_desc.iid_id AS iid_id,
tbl_desc.cid_id AS cid_id,
tbl_desc.cs AS a_better_name,
tbl_char.cid AS something_descriptive,
tbl_char.charname AS name
FROM tbl_desc,tbl_char
WHERE tbl_desc.cid_id = tbl_char.cid;
Once that is done, in Django your model could look like this:
class QryDescChar(models.Model):
iid_id = models.ForeignKey('WhateverIidIs', related_name='+',
db_column='iid_id', on_delete=models.DO_NOTHING)
cid_id = models.ForeignKey('WhateverCidIs', related_name='+',
db_column='cid_id', on_delete=models.DO_NOTHING)
a_better_name = models.CharField(max_length=10)
something_descriptive = models.IntegerField()
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = 'qry_desc_char'
You don't need the "_id" part on the end of the id column names, because you can declare the column name on the Django model with something more descriptive using the "db_column" argument as I did above (but here I only it to prevent Django from adding another "_id" to the end of cid_id and iid_id -- which added zero semantic value to your code). Also, note the "on_delete" argument. Django does its own thing when it comes to cascading deletes, and on an interesting data model you don't want this -- and when it comes to views you'll just get an error and an aborted transaction. Prior to Django 1.5 you have to patch it to make DO_NOTHING actually mean "do nothing" -- otherwise it will still try to (needlessly) query and collect all related objects before going through its delete cycle, and the query will fail, halting the entire operation.
Incidentally, I wrote an in-depth explanation of how to do this just the other day.
You are trying to fetch records from a view. This is not correct as a view does not map to a model, a table maps to a model.
You should use Django ORM to fetch QryDescChar objects. Please note that Django ORM will fetch them directly from the table. You can consult Django docs for extra() and select_related() methods which will allow you to fetch related data (data you want to get from the other table) in different ways.