Using ManyToManyFields() with Django - python

I'm building a social network where user are supposed to be able to follow each other. So I define a class user with a field: ManyToMany to stock the users that follow this user. This is what I have done in my model.py:
followings = models.ManyToManyField('self', blank=True)
This is my view.py:
#login_required
def follow_test(request):
name = request.POST.get('name', '')
user_followed = Dater.objects.get(username=name)
current_user = Dater.objects.get(id=request.user.id)
print current_user.followings # display my_app.Dater.None
current_user.followings.add(user_followed)
print current_user.followings # display my_app.Dater.None
I retrieve correctly my users (current (The one who follow someone) and the followed one) but I can't add the followed user in the set followings of the current user. Can you see something I don't do properly in my view?

followings is a manager; to show the members of that relationship, you need to call .all() on it (or another manager/queryset method like order_by).
print current_user.followings.all()

Related

How to work with MultipleCheckBox with Django?

I'm new to Django and I'm trying to make an application that registers the attendance of entrepreneurs (I'm currently working on this). There are some services that I would like to select, sometimes the same person requires more than one service per appointment. However, part of the application uses the Models and part uses the Forms, I'd like to keep the two ones separate to keep the code organized, but I have no idea how to do it, I even created a separate class just for the tuple that holds the values, but no I managed to implement, can anyone help me? Here are the codes:
models.py
from django.db import models
from django_cpf_cnpj.fields import CPFField, CNPJField
class CadastroEmpreendedor(models.Model):
ABERTURA = 'ABERTURA MEI'
ALTERACAO = 'ALTERAÇÃO CADASTRAL'
INFO = 'INFORMAÇÕES'
DAS = 'EMISSÃO DAS'
PARC = 'PARCELAMENTO'
EMISSAO_PARC = 'EMISSÃO DE PARCELA'
CODIGO = 'CÓDIGO DE ACESSO'
REGULARIZE = 'REGULARIZE'
BAIXA = 'BAIXA MEI'
CANCELADO = 'REGISTRO BAIXADO'
descricao_atendimento = (
(ABERTURA, 'FORMALIZAÇÃO'),
(ALTERACAO, 'ALTERAÇÃO CADASTRAL'),
(INFO, 'INFORMAÇÕES'),
(DAS, 'EMISSÃO DAS'),
(PARC, 'PARCELAMENTO'),
(EMISSAO_PARC, 'EMISSÃO DE PARCELA'),
(CODIGO, 'CÓDIGO DE ACESSO'),
(REGULARIZE, 'REGULARIZE'),
(BAIXA, 'BAIXA MEI'),
(CANCELADO, 'REGISTRO BAIXADO'),
)
cnpj = CNPJField('CNPJ')
cpf = CPFField('CPF')
nome = models.CharField('Nome', max_length=120)
nascimento = models.DateField()
email = models.EmailField('Email', max_length=100)
telefone_principal = models.CharField(max_length=11)
telefone_alternativo = models.CharField(max_length=11, blank=True)
descricao_atendimento
def __str__(self) -> str:
return self.nome
class DescricaoAtendimento(models.Model):
descricao = models.ForeignKey(CadastroEmpreendedor, on_delete=models.CASCADE)
forms.py
from django import forms
from .models import DescricaoAtendimento
class EmpreendedorForm(forms.ModelForm):
class Meta:
model = DescricaoAtendimento
fields = ['descricao']
widgets = {'descricao': forms.CheckboxSelectMultiple(),}
views.py
from django.shortcuts import render
from django.contrib import messages
from .forms import EmpreendedorForm
def cadastro_empreendedor(request):
if str(request.method) == 'POST':
form = EmpreendedorForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, 'Produto salvo com sucesso!')
form = EmpreendedorForm()
else:
messages.success(request, 'Erro ao salvar produto!')
else:
form = EmpreendedorForm()
context = {
'form': form
}
return render(request, 'empreendedor.html', context)
If you have any tips, I really appreciate it, I started with Django almost a month ago, so there's a long way to go.
P.S.: I integrated with PostgreSQL and in the Django administration part I can save all the fields in the DB, but I can't implement that part of the checkbox.
At this moment, I get the error:
It is impossible to add a non-nullable field 'descricao' to descricaoatendimento without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
In the template, I gonna work with bootstrap4 to create the forms. But I'd like to resolve this before. I'm still learning English, so sorry for some mistakes.
It sounds like you have many Entrepreneurs, each of which can choose many Services. This is a ManyToMany Relationship and you can create it in Django by having one model for each and creating the link between them like this
class CadastroEmpreendedor(models.Model):
...
descricao_atendimento = models.ManyToManyField(DescricaoAtendimento)
class DescricaoAtendimento(models.Model):
nome = models.CharField('Nome', max_length=120, default="unnamed service")
In this case, every object/row in DescricaoAtendimento is a service. Each entrepeneur can have many services associated with them.
This way you don't need to create a model form for DescricaoAtendimento to choose services for an entrepeneur. As it's linked to them by a manytomany relationship you can have an CadastroEmpreendedor model form with just the escricao_atendimento field and the various services become available as options.
Django handles this by creating a 'through table' which is basically a table with two fields of foreign keys, one pointing to an entrepeneur, and the other to a service. You can also create this table yourself as a through table - which is useful if you want to extend data about the relationship - eg, a begin and end date for the entrepeneur's use of a service.
The error you are getting when you migrate isn't an error, per se. It seems you created an number of DescricaoAtendimento objects and then added the descricao field later. When you then try and migrate, django wants you to provide a default value for the already existing rows, or allow the field to be empty (via blank=True in the model). You can assign a dummy value and then go back and change it in /admin later, or, if you don't have a lot of data, recreate your database and remigrate. Above I've used a default value to avoid this situation.
However, if you are dead set against extra tables, you might want to look at an extension like django multiselectfield

Creating Groups and Modifying View Access Django

I am building a web app using Django 3.0.7 and Python 3.8 that will use some business intelligence tool, like Tableau or Power BI, as a reporting source.
There is absolutely no issue with the code I am using, however I need to be able to reduce visibility to certain pages, based on a created group. For example:
If I have three pages/dashboards:
127.0.0.1:8000/director/report1, 127.0.0.1:8000/manager/report2, 127.0.0.1:8000/employee/report3
and I have three users:
Director, Manager, Employee
How can I create the site in such a way that when a user registers to the site, their profile is created and subsequently assigned a group THEN restrict access to certain pages based on the user group (this would be easier than assigning permission to every user). For example:
The user Director would belong to a group called, directors, and would have access to 127.0.0.1:8000/director/report1, 127.0.0.1:8000/manager/report2, 127.0.0.1:8000/employee/report3.
The user Manager would belong to a group called, managers, and have access to 127.0.0.1:8000/manager/report2, 127.0.0.1:8000/employee/report3.
The user Employee would belong to a group called, employees, and have access to 127.0.0.1:8000/employee/report3.
I found some information related to permissions here: https://docs.djangoproject.com/en/2.1/_modules/django/contrib/auth/decorators/ but I cannot find information related to creating groups AND assigning permissions.
You could extend the default user class in django with a new model named staff and add a charfield with director, manager and employee as given below:
from django.contrib.auth.models import User
class Staff(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
DIRECTOR = "DR"
MANAGER = "MG"
EMPLOYEE = "EM"
DESIGNATION_CHOICES = [
(DIRECTOR, "Director"),
(MANAGER, "Manager"),
(EMPLOYEE, "Employee"),
]
designation = models.CharField(
max_length=2,
choices=DEISGNATION_CHOICES,
default=DIRECTOR,
)
Then you can provide the necessary if,else conditions in your views which will prevent/allow members of certain category to make requests for certain pages.
One way to implement this could be:
#assuming you are using this view function corresponding to '/director/' url
def director_page(request):
if request.user.is_authenticated:
user = request.user
staff = Staff_objects.get(user__id=1)
if staff.designation == "Director":
...#allow director to proceed
else:
...#return an error response
else:
... # Do something for anonymous users.

How to populate choice form from db in Django?

I can't figure out how to populate choice form from db. I know about ModelChoiceForm but the problem seems to be slightly different.
I want user to choose which sector does he work in. For example: 'Finance','Electronics' etc. which I would do simple:
SECTOR_CHOICES = (('finance',_('Finance'),
'electronics',_('Electronics')...
))
But the problem is that I want admin of the web to be able to add new choices, remove choice etc.
What came to my mind is to create a simple Model called Sector:
class Sector(models.Model):
name = models.CharField(max_length=40)
and User would have new attribute sector = models.ModelChoice(Sector).
But I'm scared what would happend when admin changes or removes a sector which is already used, and more, what if he removes it and the sector attribute is required?
How to solve this problem?
I would just override the delete_model as custom action and there check if the selected sector object is in use.
def delete_model(modeladmin, request, queryset):
for obj in queryset:
if UserModel.objects.filter(sector=obj).exists():
# do not delete, just add some message warning the admin about it
else:
obj.delete()
class UserModelAdmin(admin.ModelAdmin):
actions = [delete_model]
# ...

What is best practice for restricting user permissions when editing model instances

I am building a simple app using User Authentication.
My app has 3 models:
Users : The standard Django user model
Locations: A model for an office (address, site name, etc)
Employees: A model for an employee (name, email, etc)
I also have a series of views that allow a user to login, create, and edit locations/sites, etc.
What I want to know, is what is the best practice to restrict editing of model instances to those which the user has created? E.g. with no amendment, two users could create data and both could edit the others. How do I restrict this to editing their own?
I know the long form way is to put a ForeignKey(User) on each model to restrict the view with a queryset, but this seems lengthy and cumbersome. Is there a Django trick I am missing? Perhaps a decorator?
What's the best practice?
The easiest way would be to edit your models so that they have an owner or user field that is a ForeignKey to the creator.
class Locations(models.Model):
owner = ForeignKey(User)
...
And in your views:
def edit_location(request, location_id):
location = Locations.objects.get(pk=location_id)
if request.user is not location.owner:
# return a 401 or redirect to somewhere
else:
# do stuff
You can use Django's ManyToManyField option if each Location and User can have multiple relationships. For example:
class Locations(models.Model):
owners = models.ManyToManyField(User)
user = User.objects.create(username='Ian')
location = Locations.objects.create(...)
location.owners.add(user)
And then the User/Locations are available on both:
>>> location.owners.all()
[<User: Ian>]
>>> user.locations_set.all()
[<Locations: ...>]
The set on User will be automatically created and it will be named <Model Name>_set.
You can then use the in operator to check ownership:
def edit_location(request, location_id):
location = Locations.objects.get(pk=location_id)
if request.user in location.owners.all():
# return a 401 or redirect to somewhere
else:
# do stuff

Inline-like solution for Django Admin where Admin contains ForeignKey to other model

I have several Customers who book Appointments. Each Appointment has exactly one customer, though a customer can be booked for multiple appointments occurring at different times.
class Customer(model.Model):
def __unicode__(self):
return u'%s' % (self.name,)
name = models.CharField(max_length=30)
# and about ten other fields I'd like to see from the admin view.
class Appointment(models.Model):
datetime = models.DateTimeField()
customer = models.ForeignKey("Customer")
class Meta:
ordering = ('datetime',)
Now when an admin goes to browse through the schedule by looking at the Appointments (ordered by time) in the admin, sometimes they want to see information about the customer who has a certain appointment. Right now, they'd have to remember the customer's name, navigate from the Appointment to the Customer admin page, find the remembered Customer, and only then could browse their information.
Ideally something like an admin inline would be great. However, I can only seem to make a CustomerInline on the Appointment admin page if Customer had a ForeignKey("Appointment"). (Django specifically gives me an error saying Customer has no ForeignKey to Appointment). Does anyone know of a similar functionality, but when Appointment has a ForeignKey('Customer')?
Note: I simplified the models; the actual Customer field currently has about ~10 fields besides the name (some free text), so it would be impractical to put all the information in the __unicode__.
There is no easy way to do this with django. The inlines are designed to follow relationships backwards.
Potentially the best substitute would be to provide a link to the user object. In the list view this is pretty trivial:
Add a method to your appointment model like:
def customer_admin_link(self):
return 'Customer' % reverse('admin:app_label_customer_change %s') % self.id
customer_admin_link.allow_tags = True
customer_admin_link.short_description = 'Customer'
Then in your ModelAdmin add:
list_display = (..., 'customer_admin_link', ...)
Another solution to get exactly what you're looking for at the cost of being a bit more complex would be to define a custom admin template. If you do that you can basically do anything. Here is a guide I've used before to explain:
http://www.unessa.net/en/hoyci/2006/12/custom-admin-templates/
Basically copy the change form from the django source and add code to display the customer information.
Completing #John's answer from above - define what you would like to see on the your changelist:
return '%s' % (
reverse('admin:applabel_customer_change', (self.customer.id,)),
self.customer.name # add more stuff here
)
And to add this to the change form, see: Add custom html between two model fields in Django admin's change_form
In the ModelAdmin class for your Appointments, you should declare the following method:
class MySuperModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj:
# create your own model admin instance here, because you will have the Customer's
# id so you know which instance to fetch
# something like the following
inline_instance = MyModelAdminInline(self.model, self.admin_site)
self.inline_instances = [inline_instance]
return super(MySuperModelAdmin, self).get_form(request, obj, **kwargs)
For more information, browser the source for that function to give you an idea of what you will have access to.
https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L423
There is a library you can use it.
https://github.com/daniyalzade/django_reverse_admin
But if you want to use link to object in showing table you can like this code:
def customer_link(self, obj):
if obj.customer:
reverse_link = 'admin:%s_%s_change' % (
obj.customer._meta.app_label, obj.customer._meta.model_name)
link = reverse(reverse_link, args=[obj.customer.id])
return format_html('More detail' % link)
return format_html('<span >-</span>')
customer_link.allow_tags = True
customer_link.short_description = 'Customer Info'
And in list_display:
list_display = (...,customer_link,...)

Categories

Resources