I have 2 groups of 5 employees. In each group, one of the employees is that group's supervisor.
In the create view, once the supervisor field is populated, I would like the employee foreign key field to show only those employees belonging to that supervisor.
It would be nice to have the appropriate employees displayed based on the user (supervisor) without the supervisor field having to be populated first.
I have tried model forms to try to appropriately modify the employee foreign key field query set, obviously to no avail. Please help!
The code is as follows:
class Instruction(models.Model):
supervisor = models.ForeignKey(
Profile, on_delete=models.CASCADE,
related_name="supervisorinstructions"
)
employee = models.ForeignKey(
Profile, on_delete=models.CASCADE,
related_name="employeeinstructions"
)
instruction = models.CharField(max_length=300)
def __str__(self):
return f"{self.instruction}"
def get_absolute_url(self):
return reverse("myapp:instructiondetail", kwargs={"pk": self.pk})
class InstructionCreate(CreateView):
model = models.Instruction
fields = [
"supervisor",
"employee",
"instruction",
]
template_name = "myapp/instruction_create_form.html"
More information.
For more context (pardon the pun)...
I have already been able to get a list of employees reporting to a particular supervisor.
Each employee reports to only one supervisor, so none of these employees would appear in any other list of employees reporting to another supervisor.
From this, I have been able to put a link to a (template) view where the context is a chosen employee and use the context to produce a report specific to that employee as follows:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
profile = models.Profile.objects.get(**kwargs)
context["profile"] = profile
employee = profile.employee
context["employee"] = employee
supervisor = profile.supervisor
context["supervisor"] = supervisor
# report queries
return context
Since only that supervisor can generate that report, it should also be possible for only that supervisor to be able to create an instruction for the employee, without even having to specify in the form who (the supervisor) is creating the instruction for who (the employee), and for the appropriate attributes required by my model to be set automatically from the context data.
This is where I have a problem. I am using CBVs and only a supervisor
can create an object for an employee.
When the supervisor wants to create an instruction for the employee, I want to be able to use the context (as done in the case of the report) to fix who the instruction is for and who is giving the instruction.
At the moment, when the create form opens, regardless of whether the supervisor is identified, all the employees appear, making it possible for a supervisor to create instructions for employees of the other supervisor and that's what I am trying to prevent.
I am too new in Django to figure this out on my own. In my novice opinion, it should be possible to prepopulate the create form with the supervisor and employee details from the context which negates the drop-down problem.
New, the employee model as requested ---`
class Profile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="userprofiles")
employee = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True,
related_name="employeeprofiles",
)
supervisor1 = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True,
related_name="supervisorprofiles",
)
def __str__(self):
return f"{self.user}"
def get_absolute_url(self):
return reverse("myapp:profiledetail", kwargs={"pk": self.pk})`
Corrected the name of the last model, was Employee but should have been Profile as indicated in the Instruction model.
Hope this is the answer:
class InstructionCreateForm(ModelForm):
class Meta:
model = models.Instruction
fields = ["supervisor", "employee", "instruction"]
def clean(self):
cleaned_data = super(InstructionCreateForm, self).clean()
supervisor = cleaned_data.get('supervisor')
employee = cleaned_data.get('employee')
# check if the supervisor is the employee's supervisor and raise error if not
if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
self.add_error(None, ValidationError('The employee is not your direct subordinate.'))
return cleaned_data
class InstructionCreate(CreateView):
model = models.Instruction
template_name = "internalcontrol/instruction_create_form.html"
form_class = forms.InstructionCreateForm
It worked! But only after this change:
I replaced if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
with if employee != models.Profile.objects.filter(employee=employee, supervisor=supervisor):
for it to work.
The CBV create template:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="btn-group">
<input type="submit" class="btn btn-primary btn-sm" value="Add">
List
</div>
</form>
The problem was to prevent a supervisor from giving instructions to someone else's subordinates.
Because I am new in Django, I focused on methodology I am not yet good enough in and forgot to focus on the desired outcome.
Using the code below achieves the objective in the first sentence:
class Instruction(models.Model):
employee = models.OneToOneField(Profile, on_delete=models.CASCADE, blank=True, null=True,
related_name="employeeinstructions")
supervisor = models.ForeignKey(Profile, on_delete=models.CASCADE, blank=True, null=True,
related_name="supervisorinstructions")
class InstructionCreateForm(ModelForm):
class Meta:
model = models.Instruction
fields = ["employee", "instruction"]
def clean(self):
cleaned_data = super(InstructionCreateForm, self).clean()
supervisor = cleaned_data.get('supervisor')
employee = cleaned_data.get('employee')
if supervisor != models.Profile.objects.filter(employee=employee).supervisor:
self.add_error(None, ValidationError('The employee is not a subordinate of this supervisor.'))
return cleaned_data
class InstructionCreate(CreateView):
model = models.Instruction
template_name = "internalcontrol/instruction_create_form.html"
form_class = forms.InstructionCreateForm
def form_valid(self, form):
user = self.request.user.id
profile = models.Profile.objects.get(user=user, employee=True)
form.instance.supervisor = profile
return super().form_valid(form)
The CBV create template is:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="btn-group">
<input type="submit" class="btn btn-primary btn-sm" value="Add">
List
</div>
</form>
The correct answer to my original question - the first prize I was looking for - is in Django docs (https://docs.djangoproject.com/en/3.2/ref/forms/fields/#fields-which-handle-relationships) and in Django-Filter (https://django-filter.readthedocs.io/en/stable/guide/usage.html#filtering-the-related-queryset-for-modelchoicefilter)
That closes this chapter.
Until you update your question, I will assume you have a model similar to this:
class Employee(models.Model):
# some fields not related to the current question
supervisor = models.ForeignKey(
'Employee', on_delete=models.CASCADE,
related_name="direct_reports"
)
Now if you have an Employee object named supervisor, you can do supervisor.direct_reports to get a queryset of Employee objects who's supervisor is supervisor. For your purposes, you probably want to send this in the context of your form in order to populate a dropdown list.
Alternatively, only pass the supervisor in the context and then access supervisor.direct_reports directly in the template.
Related
Newbie problem. Per the following script in a template ...
{% if request.user not in Result %}
<p>You have no account in the system.</p>
{% endif %}
... the statement "You have no account in the system." is appearing 100 times on screen---because there are 100 records and therefore the condition is being checked 100 times.
Is there a way to modify the script so that the statement appears just once? Meaning, it checks the entire database once for evidence that request.user appears anywhere in the model in aggregrate (and not whether it appears in each of the 100 records in the database)?
Maybe there's an easier/better way to do this in views.py vs a template, but that's beyond my knowledge. Thank you. Below is the model called Result.
models.py
class Result(models.Model):
custom_user = models.ForeignKey(CustomUser, default=None,
null=True, on_delete=models.SET_NULL)
decision = models.ForeignKey(Decision, default=None,
null=True, on_delete=models.SET_NULL,
verbose_name="Decision")
vote_eligible = models.BooleanField(default=True)
vote = models.CharField(default="", max_length=100,
blank=True, verbose_name="Your
Vote:")
voted_already = models.BooleanField(default=False)
#staticmethod
def get_absolute_url():
return "/home"
def __str__(self):
return f"{self.custom_user}"
views.py
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
return context
forms.py
class VotingForm(forms.ModelForm):
class Meta:
model = Result
fields = ['decision', 'vote']
views.py
Since the requirement is to display whether the logged in user has account in 'Result' model or not. I have filtered the rows specific to the user. You can loop over the user_specific in your template. If user is present in 'Result' model 'user_specifc' will have elements. If user is not present in 'Result' table, 'user_specific' will be empty. In you template, you can check whether 'user_specific' is empty list or not.
class VoteForm(LoginRequiredMixin, CreateView):
model = Result
form_class = VotingForm
template_name = 'users/vote_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context["Result"] = Result.objects.all()
context['user_specific'] = Result.objects.filter(custom_user=self.request.user)
return context
template.html
{% if not user_specific %}
<p>You have no account in the system.</p>
{% endif %}
So I have a model in a different app that records a user's 5 favorite artists. The model is uses the user as a foreign key through my own custom userprofile model. So I want to display the list of favourite artists linked to the request profile when I log in and I want to display it. But im having trouble filtering it because I keep getting an error saying the request user needs to be a userprofile instance.
Heres my music model
from django.db import models
from accounts.models import UserProfile
# Create your models here.
class Artist(models.Model):
artist_name = models.CharField(default='', max_length=200)
artist_name2 = models.CharField(default='', max_length=200)
artist_name3 = models.CharField(default='', max_length=200)
artist_name4 = models.CharField(default='', max_length=200)
artist_name5 = models.CharField(default='', max_length=200)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
as_pro = models.OneToOneField(UserProfile, on_delete=models.CASCADE, related_name='artist')
def __str__(self):
return f"{self.as_pro}-{'Artist List'}"
and heres my profile view in the accounts app
def profile(request):
userprofile = UserProfile.objects.get(user=request.user)
obj = Artist.objects.filter(as_pro=request.user)
context = {
'userprofile': UserProfile,
'obj' : obj,
}
return render(request, 'accounts/profile.html', context)
heres also my profile template where I want to display it but like I said I'm finding it hard to crack why I cant display a simple list.
</div>
<p>{{ obj.artist_name }}</p>
<p>{{ obj.artist_name2 }}</p>
<p>{{ obj.artist_name3 }}</p>
<p>{{ obj.artist_name4 }}</p>
</div>
You can filter with:
objs = Artist.objects.filter(as_pro__user=request.user)
or if you need the UserProfile of the logged in user anyway, you can work with:
userprofile = UserProfile.objects.get(user=request.user)
obj = Artist.objects.filter(as_pro=userprofile)
I'm trying to make a Django website which has all products with all their details(each product has it's own page). However, since there are too many products and they keep changing, I want to automate the creation and deletion of pages according to the database model (models.py).
Am I supposed to use a class based view? Am I supposed to use a function based view with a loop in it?
# models.py
import uuid
from django.db import models
class Brand(models.Model):
brand_id = models.AutoField(primary_key=True, unique=True)
brand_name = models.CharField(max_length=100, unique=True)
class Product(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100, null=False, blank=False)
brand_id = models.ForeignKey(Brand)
# A lot more columns will follow (didn't lock on them yet)
# This is what I had thought of initially
If I understand what you are trying to do (create product pages automatically), you can override the save method in your Products model to create a new Page each time a Product is added, and then use models.CASCADE in the foreign key so that if a product is deleted, the associated page is deleted, like so:
from django.utils.text import slugify
class Page(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
...
class Product(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(blank=True, null=True)
...
def save(self, *args, **kwargs):
self.slug = slugify(instance.name)
page = Page.objects.create(product=self,...)
page.save()
super(GeeksModel, self).save(*args, **kwargs)
You can also use Django signals, like so in your models.py file:
#receiver(post_save, sender=Product)
def create_product_page(sender, instance, created, **kwargs):
if created:
instance.slug = slugify(instance.name)
page = Page.objects.create(product=instance,...)
page.save()
instance.save()
In your views.py, for example, to get a page:
def product_page(request, product_id, product_slug):
page = Page.objects.get(product__id=product_id)
...
context = {
'page_data': page
}
return render(request, 'page_template.html', context)
And in your urls.py for the custom product pages:
...
path('/products/pages/<int:product_id>/<str:product_slug>/', product_page, name="product_page")
...
So the url would look like:
http://example.com/products/pages/3/my-amazing-widget/
If you created the link dynamically in a template with something like:
{% for product in products %}
<p><a href='{% url 'product_page' product.id product.slug %}'>{{ product.name }}</a></p>
{% endfor %}
And there are other ways as well. In the model example above, I included a SlugField you can use to generate the custom URL as well. If you elaborate on your question I can update my answer.
I'm building an app for personnel. Someone belongs in a company, which have different departments. A department from one company may have the same name with the department of another company.
The models:
class Company(models.Model):
COMPANIES = (('Comp1', 'Comp1'), ('Comp2', 'Comp2'), ('Comp3', 'Comp3'),)
name = models.CharField(primary_key=True, max_length=5, choices=COMPANIES)
def __str__(self):
return self.name
class Departments(models.Model):
name = models.CharField(max_length=10)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Personel(models.Model):
name = models.IntegerField(primary_key=True, unique=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
department = models.ForeignKey(Departments, on_delete=models.CASCADE)
The (very basic) form:
class PersonelForm(forms.ModelForm):
class Meta:
model = Personel
fields = ['name', 'company', 'department']
Companies and Departments are managed from the admin page.
The view:
def new_personel(request):
template = 'TestApp/new_personel.html'
form = PersonelForm(request.POST or None)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/')
context = {
'form': form
}
return render(request, template, context)
The template:
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
With the current code, in the department select field are shown all the departments from all companies in the database. What I'm trying to achieve is when the user selects the company, the department select field dynamically being filled with the departments from the selected company.
All the solutions I managed to find requires the company being selected before the form loads (e.g. set company in a form and continue to another form for the rest fields, use of ModelChoiceField, use of limit_choices_to and define init).
Is there a way to achieve this through Django or with the use of JavaScript in the html?
(of course more than welcome a solution that can do that in the admin page also)
You could use the ModelSelect2 widget from django-autocomplete-light.
You would define a form
from dal import autocomplete
class PersonelForm(forms.ModelForm):
department = forms.ModelChoiceField(queryset=Department.objects.all(),
widget=autocomplete.ModelSelect2(url='department-autocomplete', forward=('company', )))
Then you need to define the department-autocomplete URL view
class DepartmentAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
queryset = Department.objects
company = self.forwarded.get('vzw')
if company:
queryset = queryset.filter(company__pk=company)
return queryset.filter(name__icontains=self.q) if self.q else queryset
The self.q in this view is the query that you're typing in the form. Also don't forget to import the necessary javascript and css as stated in the documentation.
I'm working on an application and I'm stuck in the very beginning. Please help me out here.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=False)
designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=False)
User is from django.contrib.auth.models
views.py
class EmployeeCreateView(LoginRequiredMixin, generic.CreateView):
model = Employee
fields = '__all__'
success_url = reverse_lazy('core:view-employees')
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save(commit=False)
self.object.company = self.request.user.company
self.object.save()
form.save_m2m()
return super().form_valid(form)
employee_form.html
<form action="{% url 'core:add-employees' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" class="bnt btn-small">
</form>
rendered html form
Here's the problem.
Instead of User being a select list, I want fields of User model, i.e. username, email, password etc, while the Designation should remain as the select list.
Thanks in advance.
You can use ModelChoiceField for the user field in your form. Creating a form class would make it rather easier.
See https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield for reference.
there are many ways to this. If you are only using designation field as an extra field to User. just adding designation to the User model will work easily.
to do this you can use multi-table inheritance as Following.
class Employee(User): # change Here
# Remove -> user = models.OneToOneField(User, on_delete=models.CASCADE, null=False)
designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=False)
Now you will get what you wanted. but note that this will not best use case for many situations as it creates a left outer join in DB table.
but for this situation, I don't think it will affect anything.
to know more about Django multi-table inheritance see: https://docs.djangoproject.com/en/2.1/topics/db/models/#multi-table-inheritance
for other ways to extend user model see: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html