Django Assign M2M after Saving - django

I'm working on a project in Django and I have the following problem:
I have these two classes, Team and Project. When I create one project I want to automatically assign users from the team what was selected when I created the Project to the new project.
I override Project's save method and after the project was created assign users to the project(I did after saving because, before gave me an error). I tried of several ways but none of them works.
Tried by:
self.user.add(*self.team.users.all())
self.save()
And this doesn't work.
Tried iterating:
for uTeam in self.team.users.all():
self.users.add(uTeam)
and doesn't work either.
The only way that work for me is this, but only in the Django Shell:
P = Project.objects.get(pk=1)
T = Team.objects.get(pk=1)
P.user.add(*T.user.all())
P.save()
This is the solution that I have below but doesn't work in Django(gives an infinite loop)
class Team(models.Model):
name = models.CharField(max_length=200,
help_text=_('name of the team'))
user = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name="members_of_team",
help_text=_('users of the team'),
null=True
)
and more....
class Project(models.Model):
id = models.AutoField(primary_key=True, null=False)
name = models.CharField(max_length=200,
help_text=_('name of project'),
)
team = models.ForeignKey(Team,`enter code here`
on_delete=models.PROTECT,
help_text=_('team of project'))
created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
related_name='creator_project',
blank=True,
null=False,
help_text=_('project created by'))
customer = models.ForeignKey(Customer,
on_delete=models.PROTECT,
help_text=_('customer'))
user = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='users_team',
blank=True,
null=False,
help_text=_('users of this project'))
def save(self, *args, **kwargs):
if self.checkIntegrity():
super(Project, self).save(*args, **kwargs)
if self.user.all().count() < self.team.user.all().count():
T = Team.objects.get(pk=self.team.id)
P = Project.objects.get(pk=self.id)
P.user.add(*T.user.all())
P.save()
Thank you for your help

I got it, I read that m2m fields are filled after save() and post_save() and there is a signal that trigger when a m2m field is changed so I write the following:
#receiver(m2m_changed, sender=Project.user.through)
def m2mChange(sender, **kwargs):
instance = kwargs['instance']
T = Team.objects.get(pk=instance.team.id)
if kwargs['pk_set'] is None:
instance.user.add(*T.user.all())
And now it works fine.
Thank you for all.

Related

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)```

Django filter two levels of DB relationships

I have three models that I'm trying to hook up so I can pull-out StudentItem information based upon ID of the Course that was passed in.
The models look like the following:
class Student(models.Model):
first_name = models.CharField(max_length=128, unique=False)
last_name = models.CharField(max_length=128, unique=False)
class Course(models.Model):
name = models.CharField(max_length=256, unique=False)
course_student = models.ManyToManyField(Student)
class StudentItem(models.Model):
item_student = models.ForeignKey('Student',on_delete=models.CASCADE)
description = models.CharField(max_length=256, unique=False, blank=True)
Right now, my view looks like:
class CourseDetailView(LoginRequiredMixin,DetailView):
model = models.Course
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['student_item_list'] = StudentItem.objects.prefetch_related(Prefetch('item_student__id__course_student',queryset=Course.objects.filter(pk=self.kwargs['pk'])))
return context
I can get everything connected via prefetch across all of the models but the queryset filtering is not working. I am getting all records for every course regardless of the course ID that was passed in.
Hopefully it's a small tweak. Really appreciate the help!

django checkbox select multiple models

Hi I have the following django model:
class Issue(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField(auto_now=True)
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignee')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner', null=True, blank=True)
description = models.TextField()
state = models.IntegerField(choices=STATUS_CHOICES, default=1)
priority = models.IntegerField(choices=RELEVANCE_CHOICES, default=2)
expired_date = models.DateField(auto_now=False, null=True, blank=True)
and a form which allow a user to create an Issue instance:
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title', 'description', 'assignee', 'state', 'priority', 'expired_date')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = "Titolo"
self.fields['description'].label = "Descrizione"
self.fields['state'].label = "Stato"
self.fields['priority'].label = "Priorità"
self.fields['expired_date'].label = "Termine"
self.fields['expired_date'].widget.attrs.update({'class': 'datepicker'})
self.fields['assignee'] = forms.MultipleChoiceField(
choices=self.fields['assignee'].choices,
widget=forms.CheckboxSelectMultiple,
label=("Assegnatario")
)
def clean(self):
cleaned_data = super().clean()
user_id = [i for i in cleaned_data['assignee']]
cleaned_data['assignee'] = [User.objects.get(id=i) for i in user_id]
return cleaned_data
I render this form and the field assignee is a checkbox.
I would like to be able to choose several assignee for the same issue, but I got an error because the Issue model expect just one User instance
How can I modify my model Issue in order to get more than one user ?
Thanks
you can create a new class and name it Issue_Instance where every Issue Object can have an assignee as a foreign key the problem that the relation is one to many because you have to choose more than one assignee and Django doesn't support the idea of having Array or List of Foreign Keys(I don't know any frame works that do :=) ) so I would suggest creating a new class or make the foreign key relation one-to-many key field read about it it will be very useful to solve your problem

Two step object creation in Django Admin

I'm trying to change the implementation of an EAV model using a JSONField to store all the attributes defined by an attribute_set.
I already figured out how to build a form to edit the single attributes of the JSON, but I'm currently stuck at implementing the creation of a new object. I think I have to split object creation in two steps, because I need to know the attribute_set to generate the correct form, but I don't know if there's a way to hook in the create action, or any other way to achieve what I need.
My models look like this:
class EavAttribute(models.Model):
entity_type = models.CharField(max_length=25, choices=entity_types)
code = models.CharField(max_length=30)
name = models.CharField(max_length=50)
data_type = models.CharField(max_length=30, choices=data_types)
class AttributeSet(models.Model):
name = models.CharField(max_length=25)
attributes = models.ManyToManyField('EavAttribute')
class EntityAbstract(models.Model):
attribute_set = models.ForeignKey(
'AttributeSet',
blank=False,
null=False,
unique=False,
)
class Meta:
abstract = True
class Event(EntityAbstract):
entity_type = models.CharField(max_length=20, null=False, choices=entity_types, default=DEFAULT_ENTITY_TYPE)
code = models.CharField(max_length=25, null=True, blank=True, db_index=True)
year = models.IntegerField(db_index=True)
begin_date = models.DateField()
end_date = models.DateField()
data = JSONField()
How can I choose the AttributeSet first and then go to another form that I would populate with the attributes in the chosen attribute set?
I ended up using get_fields() and response_add() methods, like so:
def get_fields(self, request, obj=None):
if obj is None:
return ['attribute_set']
else:
return [attr.name for attr in obj._meta.get_fields() if not attr.auto_created and attr.name != 'id']
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['entity_type', 'code', 'state']
if obj is not None:
readonly_fields.append('attribute_set')
return readonly_fields
def response_add(self, request, obj, post_url_continue=None):
url = '/admin/risks/event/{}/change/'.format(obj.id)
return redirect(url)
The downside of this approach is that object is saved in the database and then opened for edit, so basically the database is hit twice and all attributes have to be nullable, except for attribute_set.
I would be happy to receive ideas for better implementations.

How do I make a model formset based on another model

I currently have two Django Models, on it like a setup model and another is the actual data for that model. Like this:
class Extra(models.Model):
has_text = models.BooleanField(u'Has Text', default=False)
has_image = models.BooleanField(u'Has Image', default=False)
has_file = models.BooleanField(u'Has File', default=False)
class OrderExtra(models.Model):
extra = models.ForeignKey('Extra')
image = models.ImageField(upload_to=get_order_extra_upload_path, blank=True, null=True)
file = models.FileField(upload_to=get_order_extra_upload_path, blank=True, null=True)
text = models.TextField(blank=True, null=True)
comments = models.TextField(blank=True, null=True)
I've been trying to make a formset of the OrderExtra that is linked up to a queryset of the Extra that I've filtered out. Then hide the fields of the unchecked boxes of the Extra.
I though about making a form for the Extra and replacing the fields on creation, but I wasn't sure how to do this properly...
If anyone could help me, or provide some direction that would be fantastic, because I'm stuck on how to do this...
Cheers.
Try to make form for OrderExtra and in init of it add checked fields from related extra object
class MyForm(forms.ModelForm):
has_text = None
class Meta():
model=OrderExtra
def __init__(self, *args , **kwargs):
super(MyForm, self).__init__(*args , **kwargs)
if self.instance and self.instance.extra.has_text:
self.has_text = forms.BooleanField(...)
You can do this also for has_image and has_file