Django models - create items which have to pass various tasks - django

I am trying to create a model for the following scenario:
An item is delivered and has to pass through various tasks in a certain order.
As soon as the item has passed through a task, the task is marked as completed.
The names of the tasks are known in advance.
For example:
Create item
task 1 'register the item'
task 2 'install the item'
task 3 'check logs of item'
I started with a simple model like this, but it looks very static to me and not really i what i am looking for.
models.py
class Item(models.Model):
item_id = models.PositiveIntegerField()
item_priority = models.PositiveSmallIntegerField(default=2)
item_date_created = models.DateField(auto_now_add=True)
task1_name = models.CharField(max_length=128)
task1_done_by = models.CharField(max_length=64, blank=True)
task1_done_date = models.DateField(null=True, blank=True)
task2_name = models.CharField(max_length=128)
task2_done_by = models.CharField(max_length=64, blank=True)
task2_done_date = models.DateField(null=True, blank=True)
# ... next 20 tasks
def __str__(self):
return str(self.item_id)
Trying Relational fields, for example a ManyToMany Field to pre define the tasks, i ended up with this:
class Task(models.Model):
name = models.CharField(max_length=128)
done_by = models.CharField(max_length=64, blank=True)
done_date = models.DateField(null=True, blank=True)
class Item(models.Model):
item_id = models.PositiveIntegerField()
item_priority = models.PositiveSmallIntegerField(default=2)
item_date_created = models.DateField(auto_now_add=True)
tasks = models.ManyToManyField(Task)
But, if i create two items with the same tasks and mark the task of the first item as done it will be marked as done in the second item as well -- this is not what i want.
How to assign a task to a specific item? Or which other model would best fit my scenario?

For executing code at object creation time you generally override the save() method. So when an Item gets created, in the save() method you create and link dynamically the Tasks to Item.tasks
class Item(models.Model):
item_id = models.PositiveIntegerField()
item_priority = models.PositiveSmallIntegerField(default=2)
item_date_created = models.DateField(auto_now_add=True)
# remove m2m !!!
def save(self, *args, **kwargs):
# If it has no pk then it is a *new* item
if not self.pk:
# Save by manually
# Call save of parent class (models.Model) creating the custom class
super(Item, self).save(*args, **kwargs)
# Now you have the PK
t1 = Task( name='T1', item=self )
t1.save()
#...
class Task(models.Model):
name = models.CharField(max_length=128)
done_by = models.CharField(max_length=64, blank=True)
done_date = models.DateField(null=True, blank=True)
# Add Item FK
item = models.ForeignKey(Item)
Note on Code Update:
1) I changed the direction: the task links to the item, not the other way round. See https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_one/
2) I still check if it is a new instance, then call super save and then I create the task for this item.
Make sure to check the self.pk value otherwise the code will get executed also when updating the object.
Also be aware of that you have to create a new Task object linked to an Item object every time. If you link one task object to two items then when item_a completes a task the task would appear to be completed also for item_b.
What you need to do is create task1 for item_a and task2 for item_b. This way you separate the tasks.
Read on:
https://docs.djangoproject.com/en/1.11/ref/models/instances/#saving-objects
https://docs.djangoproject.com/en/1.11/topics/db/models/#relationships

Related

Append to queryset object in Django

My Django Model:
class Job(models.Model):
title = models.CharField(_('Job Title'),max_length=150, blank=True, default="")
description = models.CharField(_('Description'), max_length=250, blank=True, default="")
user = models.ForeignKey('User', verbose_name="User", on_delete=models.CASCADE, null=True)
def __str__(self):
return self.title
I have a queryset of django objects:
>>> jobs = Job.objects.all()
<QuerySet [<Job: university>, <Job: companies>, <Job: inside>, <Job: outside>]>
I want to iterate over them, and update their dict (merge their dict with a new dict) with a key that they don't have. Is it possible to do something like this?
for job in jobs:
job.update('new_key' : 'new_value') #This returns error
To obtain the job object with the following keys: title, description, user, new_value.
Why do I want to do this? To be easier to iterate over those values that are correlated with jobs with only one for loop in the templates.
I don't want to use Job.objects.all().values() because that way I can't do job.user.name in the templates and get user name or user information.

Checking if a field in model is modified and creating instance of another model

I have two models
Project Model
class Project(models.Model):
name = models.CharField(max_length=200)
workflow = models.ForeignKey("WorkflowType", null=True, blank=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
def __str__(self):
return self.name
Workflow Instance Model
class WorkflowInstance(models.Model):
workflow_step = models.ForeignKey('WorkflowStep', null=True, blank=True, on_delete=models.CASCADE)
project = models.ForeignKey('Project', null=True, blank=True, on_delete=models.SET_NULL)
I want to check if the value of workflow field in "Project" models is added or changed for a particular project.
I am approaching the problem in following manner:
Checking if the previous and the new value of the "workflow" field in a project are different. If yes (modifies), then create the new instance of a project.
#receiver(pre_save, sender=Project)
def projectToBeUpdated(sender, instance, **kwargs):
if instance.id is None:
pass
else:
previous = Project.objects.get(id=instance.id)
if previous.workflow != instance.workflow:
print("workflow value modified. Please create a WorkflowInstance")
Problem: The comparison for previous and new value of the "workflow" field are happening in "pre_save" signal. But my new instance creation for workflowInstance is to be created in "post_save" signal. How can I do this?
Also, ideally I would like to store the previous value of workflow field in "pre_save" and get the new value of the field in "post_save". Reason being, save() method might fail for any reason, while I am comparing the previous and new value in "pre_save" method itself. Making changes in the database without confirming if the save() method executed successfully would be a wrong approach in my view.
You can override the model save method itself and use the following code logic, additionally if you want to identify what fields got modified you can use the dirtyfields package as well.
def save(self, *args, **kwargs):
if not self._state.adding:
changed_attr = self.get_dirty_fields()
else:
is_new = True
super().save(*args, **kwargs)```

How to populate a model field with a Django signal after updating another model field?

I have a list of tasks that are created by the admin and as the task start dates are set by individual agents, I would like to use a signal to assign the tasks to that agent.
Models.py
class Task(models.Model):
name = models.CharField(max_length=20, blank=True, null=True)
agent = models.ForeignKey("Agent", on_delete=models.SET_NULL, null=True)
start_date = models.DateField(null=True, blank=True)
notes = models.TextField(default=0, null=True, blank=True)
You can use the pre_save signal to compare the old field value vs. the new field value, something like:
#receiver(pre_save, sender=Task)
def assign_task(sender, instance, **kwargs):
# get the task before the new value is saved:
instance_with_old_value = Task.objects.get(id=instance.id)
old_start_date = instance_with_old_value.start_date
# get the task after the new value is saved:
instance_with_new_value = instance
new_start_date = instance_with_new_value.start_date
# do something with dates, assign agent:
...

Using reverse ForeignKey relation to get count of children for current object not working properly

I'm currently making a Todo app, I have multiple Todolist that can each contain multiple tasks, here's how I made my models :
class Todo(models.Model):
name = models.CharField(max_length=120)
total_tasks = models.IntegerField(default=0)
completed_tasks = models.IntegerField(default=0)
def update_total_tasks(self):
self.total_tasks = self.task_set.all()
def _str_(self):
return self.name
class Task(models.Model):
name = models.CharField(max_length=120)
completed = models.BooleanField(default=False)
todo = models.ForeignKey(Todo, on_delete=models.CASCADE, related_name="tasks")
My todo contains all current tasks as well as completed tasks, the update_total_tasks function is meant to query all tasks linked to that particular todo and update the field accordingly.
This function is called each time a task is created / updated with :
#receiver(models.signals.post_save, sender=Task)
def execute_after_save(sender, instance, created, *args, **kwargs):
instance.todo.update_total_tasks()
The receiver works and calls my update function properly though it seems the query is done the wrong way because I get this error :
AttributeError: 'Todo' object has no attribute 'task_set'
Do you have any idea on why it's not working properly ?
Thanks.
The related_name=… parameter [Django-doc] is set to 'tasks'. Hence you access the relation in reverse with .tasks.
You thus access this with:
class Todo(models.Model):
name = models.CharField(max_length=120)
total_tasks = models.IntegerField(default=0)
completed_tasks = models.IntegerField(default=0)
def update_total_tasks(self):
self.total_tasks = self.tasks.count()

Django Rest : Model field default value only while creating

I have a model with 2 char-fields. I want to give them default values when a row is getting created. This default values are sort of IDs which depends on time. This ID should never change. i.e. the default value should not be applied when subsequent updates happen on the row.
I'm trying to use update_or_create but, the defaults while creating & updating are not same. How can I put these Ids only while creating and ignore while updating?
I'm referring to this answer but no luck.
EDIT:
Following is the code for reference:
Model:
class UsersModel(models.Model):
id = models.CharField(db_column="id", max_length=25, primary_key=True)
key = models.CharField(db_column="key", max_length=100)
a = models.CharField(db_column="a",max_length=25, null=True, blank=True)
b = models.BigIntegerField(db_column="b", null=True, blank=True)
Views:
def post(self, request, format=None):
UsersModel.objects.update_or_create(a="a_val",defaults={"b":"b_val"})
Here, I want the id & key to take default values when the row is being created. When it is being updated, only b should get updated as shown in above code.
The same can be achieved by overiding the save function of models.Model to ensure that an id and key is given a value(default) in your case if the model is being created.
class UsersModel(models.Model):
id = models.CharField(db_column="id", max_length=25, primary_key=True)
key = models.CharField(db_column="key", max_length=100)
a = models.CharField(db_column="a",max_length=25, null=True, blank=True)
b = models.BigIntegerField(db_column="b", null=True, blank=True)
def save():
if not self.id:
self.id=get_default('id')
if not self.key:
self.key=get_default('key')
super(UsersModel, self).save(*args, **kwargs)
This will ensure when you create and update, the first time save is called, these variables are assigned a default value before getting saved in the db.