I am using GroupRequiredMixin from braces.views trying to limit the access to some views to a specific group of users.
My view looks like this:
class InviteCompanyAdminView(LoginRequiredMixin, GroupRequiredMixin, CreateView):
model = CompanyAdminInvitation
group_required = u"staff" # For the GroupRequiredMixin
success_url = '/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse
form.instance.send_invite(self.request)
return super(InviteCompanyAdminView, self).form_valid(form)
I am accessing the view with a user that doesn't belong to any group and yet the view is rendered. Any idea why?
My model looks like this:
class CompanyAdminInvitation(models.Model):
user_first_name = models.CharField(max_length=128)
user_last_name = models.CharField(max_length=128)
user_title = models.CharField(max_length=128)
user_email = models.EmailField()
company_name = models.CharField(max_length=128)
company_country = CountryField()
invitation_uid = models.CharField(max_length=100, null=True, blank=True, unique=True, editable=False)
has_been_used = models.BooleanField(default=False, editable=False)
invitation_date = models.DateTimeField(auto_now_add=True)
def __init__(self, *args, **kwargs):
super(CompanyAdminInvitation, self).__init__(*args, **kwargs)
self.invitation_uid = str(uuid4())
def send_invite(self, request):
# TODO use a template instead of hardcoding the email
subject = "Get started with your account!"
message = "Hi, your link to start is %s" % \
(request.build_absolute_uri(reverse("users:admin_signup",
kwargs={"invite_id": str(self.invitation_uid)})))
from_email = "no-reply#example.com"
recipient_list = [self.user_email,]
send_mail(subject=subject, message=message, from_email=from_email, recipient_list=recipient_list)
def get_absolute_url(self):
return "/"
I am not sure if it is the cause of the original posters problem, but this can be caused if testing with a superuser, which will ignore the group permissions in Braces (see issue #105 for django-braces)
Try testing with a non-superuser to fix this issue.
Related
Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.
I'd like to find a simple and robust way to create a child object. I think it is a simple problem, probably solved using Django RelationshipManager or Related objects reference.
I've gotten it to work in the past (by paying someone on fiver to help me solve this), but I feel that there is a much simpler method that escapes me.
This worked on my views.py
class MainVisitForm(SingleObjectMixin, FormView):
template_name = "clincher/visit_form.html"
form_class = VisitForm
model = Main
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form=self.get_form()
form.fk_visit_user = self.request.user
form.fk_visit_main = Main.objects.get(id=self.kwargs['pk'])
#added this to save form as we are mixxing the two forms and Models
# as the approch of singleObjectMixin is we should get object from DB as per request url as a primary key
#and we have defined model as a Main but taking the form view of VistForm as the probem occures
# as i think
if form.is_valid():
instance = Main()
instance.firstname = form.cleaned_data['firstname']
instance.middelname = form.cleaned_data['middlename']
instance.lastname = form.cleaned_data['lastname']
instance.date_of_birth = form.cleaned_data['date_of_birth']
instance.sex = form.cleaned_data['sex']
instance.address = form.cleaned_data['address']
instance.save()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('clincher:main_detail', kwargs={'pk': self.object.pk})
Basically, while the user is in the details page of the "main" object, I would like them to be able to create a child object (visit object). Ultimately 1 patient will have many visits (1:m relationship). Each time a patient visits the doc, 1 new visit will be added, that is related to that person. Later, I will show a list of visits for that patient (but not the subject of this question).
Models.py
class Main(models.Model):
firstname = models.CharField(max_length = 256, verbose_name=('First Name'))
middlename = models.CharField(max_length=256, verbose_name=('Middle Name'))
lastname = models.CharField(max_length=256, verbose_name=('Last Name'))
date_of_birth = models.DateField()
age = models.CharField(max_length=4)
sex_list = (
(str(1), 'Female'),
(str(2), 'Male'),
(str(3), 'Other'),
(str(4), 'Unknown'),)
sex = models.CharField(max_length = 24, choices=sex_list, verbose_name='Sex')
address = models.TextField(max_length = 256)
#property
def full_name(self):
#"Returns the person's full name."
return '%s %s' % (self.firstname, self.lastname)
#Redirects after form is submitted using primary key
def get_absolute_url(self):
return reverse('clincher:main_detail', kwargs={'pk': self.pk})
def __str__(self):
return self.firstname + ' ' + self.lastname +' - ' + str(self.date_of_birth)
class Visit(models.Model):
fk_visit_main = models.ForeignKey(Main, on_delete=models.CASCADE, verbose_name=('Patient Name'))
visit_date = models.DateField(auto_now = True, editable=True)
fk_visit_user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=('Practitioner'), max_length=500)
visit_label = models.CharField(max_length=256, blank=True, null=True)
visit_type = models.CharField(
max_length=256,
default=1, verbose_name='Type of Visit')
visit_progress_notes = models.TextField(max_length=10000,
blank=True, verbose_name='Progress Notes')
outcomes = models.BooleanField(default=False)
def __str__(self):
return '%s %s' % (self.visit_date, self.visit_label)
def get_absolute_url(self):
return reverse('clincher:main_detail', kwargs={'pk': self.pk})
forms.py
class VisitForm(forms.Form):
visit_types_list = (
(str(1), 'Consultation'),
(str(2), 'Procedure'),
(str(3), 'Administrative'),)
visit_type = forms.ChoiceField(choices=visit_types_list)
visit_label = forms.CharField(label='Visit Label', max_length=100)
progress_note = forms.CharField(widget=forms.Textarea)
def form_valid(self, form):
form.instance.fk_visit_user = self.request.user
form.instance.fk_visit_main = Main.objects.get(id=self.kwargs['pk'])
return super().form_valid(form)
Thus, I should end up with a child record/object that has the primary key of the parent object.
The above code works, but I am sure that there is a simple Django-ey way of doing things much simpler, and in a robust manner. I think my solution should be found in the Django RelationshipManager, but I cannot find a solution that works. I paid a guy on Fiver, and I think he didn'y get this as simple as possible.
Check django InlineFormset: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#inline-formsets
If you want to have to abillity to add an remove the formset dynamically checkout (Jquery based) :
https://github.com/elo80ka/django-dynamic-formset
If you are using class based view you will have to add the inlineformset in get_context_data() and inside form_valid() check if the formset.is_valid() and then save it to the database.
EDIT: Here is the code based on your comment
forms.py
class VisitForm(forms.ModelForm);
class Meta:
model = Visit
fields = [
'visit_type',
'visit_label',
'visit_progress_notes'
]
views.py
class CreateVisitView(CreateView):
model = Visit
form_class = VisitForm
template_name = "clincher/visit_form.html"
#one of the first function called in class based view, best place to manage conditional access
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
return super(CreateVisitView,self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
visit = form.save(commit=False)
visit.fk_visit_user = self.request.username
visit.fk_visit_main = get_object_or_404(Main, pk=self.kwargs.get('pk'))
visit.save()
return super(CreateVisitView,self).form_valid(form)
models.py
class Main(models.Model):
SEX_LIST_CHOICE = (
(str(1), 'Female'),
(str(2), 'Male'),
(str(3), 'Other'),
(str(4), 'Unknown'),
)
firstname = models.CharField(max_length = 256, verbose_name=('First Name'))
middlename = models.CharField(max_length=256, verbose_name=('Middle Name'))
lastname = models.CharField(max_length=256, verbose_name=('Last Name'))
date_of_birth = models.DateField()
age = models.PositiveSmallIntegerField()
sex = models.CharField(max_length = 24, choices=SEX_LIST_CHOICE, verbose_name='Sex')
address = models.TextField(max_length = 256)
#property
def full_name(self):
#"Returns the person's full name."
return '%s %s' % (self.firstname, self.lastname)
#Redirects after form is submitted using primary key
def get_absolute_url(self):
return reverse('clincher:main_detail', kwargs={'pk': self.pk})
def __str__(self):
return self.firstname + ' ' + self.lastname +' - ' + str(self.date_of_birth)
class Visit(models.Model):
VISIT_TYPE_CHOICE = (
(str(1), 'Consultation'),
(str(2), 'Procedure'),
(str(3), 'Administrative'),)
fk_visit_main = models.ForeignKey(Main, on_delete=models.CASCADE, verbose_name=('Patient Name'))
visit_date = models.DateField(auto_now = True, editable=True)
fk_visit_user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=('Practitioner'), max_length=500)
visit_label = models.CharField(max_length=256, blank=True, null=True)
#you are storing the type of visit as an
visit_type = models.CharField(
max_length=256,
default=1,
verbose_name='Type of Visit',
choices=VISIT_TYPE_CHOICE
)
visit_progress_notes = models.TextField(max_length=10000,
blank=True, verbose_name='Progress Notes')
outcomes = models.BooleanField(default=False)
def __str__(self):
return '%s %s' % (self.visit_date, self.visit_label)
def get_absolute_url(self):
return reverse('clincher:main_detail', kwargs={'pk': self.pk})
So a number of things here that you could clear up.
instance.middelname = form.cleaned_data['middlename'] Will never work as middlename is incorrect on the instance side.
You can use Main.objects.create(firstname=form.validated_data['firstname'], lastname= .... etc) to create your Model instances
You should probably have the relation from a User to your models be via Main, not Visit. This will allow you to add records for a Visit easier, for example, staff member logging visits instead of customer.
You should lookup CreateView to assist you with the boilerplate of creating an instance.
Rename the Main model. What is it actually? Looks like a Profile to me, but calling it Main isn't very descriptive.
Age should be an integer field. Nobody is 'dave' years old.
I am creating a Non Disclosure Agreement form that a user fills out after registering and logging in. I am using a custom signup form with AllAuth and pre-populating parts of the form. I pre-populate the first and last name into the top part of the form as shown below in first screen shot, but as a part of the digital signature I am setting up; I need to validate the typed signature field matches the name of the first_name and the last_name concatenated together per the second screen-shot. I know I need to setup a validator based on Django Form & Field Validations and I've tried several things but just can't get my mind rapped around it. Any help putting this together would be huge...thank you.
My Models
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile", verbose_name="user")
...
class NonDisclosure(Timestamp):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="nda", verbose_name="profile")
user_signature = models.CharField(max_length=250, verbose_name='Signature')
user_street = models.CharField(max_length=250, verbose_name='Street Address')
user_city = models.CharField(max_length=250, verbose_name='City')
user_state = models.CharField(max_length=2, verbose_name='State Initials')
user_zip = models.IntegerField(verbose_name='Zip Code')
phone = models.CharField(max_length=25, verbose_name='Phone Number')
cash_on_hand = models.CharField(max_length=250, verbose_name='Cash on Hand')
value_of_securities = models.CharField(max_length=250, verbose_name='Value of Securities')
equity_in_real_estate = models.CharField(max_length=250, verbose_name='Equity on Real Estate')
other = models.CharField(max_length=250, verbose_name='Other Assets')
#property
def username(self):
return self.profile.username
#property
def first_name(self):
return self.profile.first_name
#property
def last_name(self):
return self.profile.last_name
#property
def email(self):
return self.profile.email
class Meta:
verbose_name = 'Non Disclosure Agreement'
verbose_name_plural = 'Non Disclosure Agreements'
def __str__(self):
return "%s" % self.profile
def get_absolute_url(self):
return reverse('nda_detail', args=[str(self.id)])
My Views:
class NonDisclosureForm(BaseModelForm):
cash_on_hand = forms.CharField(required=False)
value_of_securities = forms.CharField(required=False)
equity_in_real_estate = forms.CharField(required=False)
other = forms.CharField(required=False)
class Meta:
model = NonDisclosure
fields = ['user_signature', 'user_street', 'user_city', 'user_state', 'user_zip', 'phone', 'cash_on_hand', 'value_of_securities', 'equity_in_real_estate', 'other']
class NdaCreate(CreateView):
form_class = NonDisclosureForm
template_name = 'nda/nda_form.html'
def form_valid(self, form):
form.instance.profile = Profile.objects.get(user=self.request.user)
form.instance.created_by = self.request.user
return super(NdaCreate, self).form_valid(form)
Firstly, you should subclass ModelForm, not BaseModelForm. Write a clean_<fieldname> method for your user_signature field, and make sure that the value is as expected. You can access self.instance.created_by to check.
class NonDisclosureForm(ModelForm):
...
class Meta:
model = NonDisclosure
fields = ['user_signature', ...]
def clean_user_signature(self):
user_signature = self.cleaned_data['user_signature']
expected_name = '%s %s' % (self.instance.created_by.first_name, self.instance.created_by.last_name)
if user_signature != expected_name:
raise forms.ValidationError('Signature does not match')
return user_signature
Then you need to update your view so that it sets instance.created_by. You can do this by overriding get_form_kwargs.
class NdaCreate(CreateView):
def get_form_kwargs(self):
kwargs = super(NdaCreate, self).get_form_kwargs()
kwargs['instance'] = NonDisclosure(created_by=self.request.user)
return kwargs
How to set default charfield in lowercase?
This is my model:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
self.username = self.username.lower()
I tried the __init__ but it doesn't work. I want to make the username in lowercase every time new record saved. Thanks.
While overwriting save() method is a valid solution. I found it useful to deal with this on a Field level as opposed to the Model level by overwriting get_prep_value() method.
This way if you ever want to reuse this field in a different model, you can adopt the same consistent strategy. Also the logic is separated from the save method, which you may also want to overwrite for different purposes.
For this case you would do this:
class NameField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = NameField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
Just do it in the save method. ie, override the save method of Model class.
def save(self, *args, **kwargs):
self.username = self.username.lower()
return super(User, self).save(*args, **kwargs)
signals also works
from django.db.models.signals import pre_save
#receiver(pre_save, sender=YourModel)
def to_lower(sender, instance=None, **kwargs):
instance.text = instance.text.lower() if \
isinstance(instance.text, str) else ''
In my case I had a recipient_name field that I needed to make all lower case when it is stored on DB
class LowerField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class Recipients(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='recipients', on_delete=models.CASCADE, )
recipient_account_number = models.IntegerField()
recipient_name = LowerField(max_length=30)
recipient_bank_name = models.CharField(max_length=30)
date = models.DateTimeField(auto_now=True, verbose_name='Transaction Date')
class Meta:
ordering = ['-date']
def __str__(self):
return self.recipient_name
def get_absolute_url(self):
return reverse('recipient-detail', kwargs={'pk': self.pk})
Similarly, you can apply to another table called Transactions in your app, like this
class Transactions(models.Model):
transaction_type = (
('transfer', 'Transfer'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='transactions', on_delete=models.CASCADE, )
bank_name = LowerField(max_length=50)
def save(self, force_insert=False, force_update=False):
self.YourFildName = self.YourFildName.upper()
super(YourFomrName, self).save(force_insert, force_update)
I have a class Task with the following implementation:
class Task(models.Model):
author = models.ForeignKey(Author, unique=False)
name = models.CharField(max_length=255)
completed = models.BooleanField(default=False)
deadline = models.DateTimeField(null=True, blank=True)
pub_date = models.DateTimeField(auto_now_add=True, editable=False)
edit_date = models.DateTimeField(auto_now_add=True, auto_now=True, editable=False)
tag = models.ManyToManyField(Tag, related_name='tags', null=True, blank=True, default=None)
# group = models.ForeignKey(Group, blank=True, default=None)
def __str__(self):
return u'%s' % (self.name)
def toggle_complete(self):
self.completed = not self.completed
def is_past_deadline(self):
return timezone.now() > self.deadline
And I am trying to do a simple form that creates a new Task with a Title. But, as you can see, the author attribute can not be null (and don't want to, of course).
Author is implemented as follows:
class Author(models.Model):
user = models.OneToOneField(User, primary_key=True)
name = models.CharField(max_length=30)
def __str__(self):
return u'%s' % (self.user)
I tried and tried to hide the author field and, overriding methods like get_form_kwargs, form_valid, get_form to set it to the current logged user, but I always fail. Simply, the id is neither sent as post data (as seein in the debug trace), nor fetched from the view itself.
My best result has been showing the author field, creating the user correctly, but getting a "success_url" not found, even with the model having a get_absolute_url method declared.
The view I am working with is implemented like:
class HomeView(CreateView, MultipleObjectMixin):
# common
model = models.Task
template_name = 'home.html'
#form
form_class = TaskForm
# list
object_list = model.objects.all()
context_object_name = 'tasks'
paginate_by = 40
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('taskr:index'))
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(HomeView, self).get_form_kwargs()
kwargs['initial']['author_id'] = self.request.user.id
return kwargs
def form_valid(self, form):
task = form.save(commit=False)
task.user = models.Author.objects.get(user=self.request.user) # use your own profile here
task.save()
return HttpResponseRedirect(self.get_success_url())
For the record, the MultipleObjectMixing part of the view works flawlessly.
I am desperate, is there any good resource for Django forms, one like http://ccbv.co.uk/? Thanks.
After a good night sleep, while cleaning up, I tried fixing the form_valid in the CreateView descendant and I got it right.
The trick is in
task.user = models.Author.objects.get(user=self.request.user)
and it failed to me because of desperate copy-pasting. The problem was that my Task model has no user attribute, but an author. So
task.author = models.Author.objects.get(user=self.request.user)
fixes it all.
Sorry for the stupid question.