i am trying to create a notifications system but am facing issues doing so.
here is my code:
I created a Notifications model where each entry is a notification. Every time a new entry is made into my SalesTask model , it invokes my signal handler which will create a new entry into my Notifications Model. I will then pass this model into my context and render it in my HTML.
class Notifications(models.Model):
notifications_id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
time = models.DateTimeField(auto_now=True)
message = models.TextField(max_length=100 ,default ='test')
object_url = models.CharField(max_length=500, default ='test')
is_read = models.BooleanField(default=False)
def CreateTaskNotification(sender,**kwargs):
if kwargs['created']:
notification = Notifications.objects.create(user = kwargs['instance'].salesExtra.username,
message = 'You have been assigned a new task',
object_url = kwargs['instance'].get_absolute_url(self)
)
post_save.connect(CreateTaskNotification,sender=SalesTask)
The issue with this is that my SalesTask Models :
class SalesTask(models.Model):
sales_status= (
('p1','Phase 1'),
('p2','Phase 2'),
('p3','Phase 3'),
('p4','Phase 4'),
)
sales_priority= (
('Urgent','Urgent'),
('Medium','Medium'),
('Low','Low'),
)
task_id = models.AutoField(primary_key=True)
salesExtra= models.ManyToManyField('SalesExtra')
sales_project= models.ForeignKey('SalesProject',on_delete=models.CASCADE)
title = models.TextField(max_length=50 , default='Your Title' )
description = models.TextField(max_length=200 , default='Your Description' )
priority = models.TextField(max_length=10 , choices= sales_priority ,default='Low' )
date_time = models.DateTimeField(auto_now=True)
status = models.TextField(max_length=10, choices= sales_status ,default='p1')
due_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.task_id)
def get_absolute_url(self):
return reverse('sales-task')
has a many to many field with an extended model of User model (SalesExtra) . This is most probably why i am getting the error message :
'ManyRelatedManager' object has no attribute 'user'
This is my views for creating a new instance of a task:
class SalesTaskDetailView(CreateView):
model = SalesTaskingComments
template_name = 'rnd/task_details.html'
fields = ['comment']
exclude = ['comment_id']
def get_context_data(self, **kwargs):
pk = self.kwargs['pk']
context = super(SalesTaskDetailView, self).get_context_data(**kwargs)
context['sales_task'] = SalesTask.objects.filter(task_id = pk)
context['comments'] = SalesTaskingComments.objects.filter(salesTask__task_id__contains= pk)
return context
def form_valid(self, form):
task = get_object_or_404(SalesTask, task_id=self.kwargs['pk'])
user = get_object_or_404(SalesExtra, user=self.request.user)
form.instance.salesTask = task
form.instance.salesExtras = user
return super(SalesTaskDetailView, self).form_valid(form)
My question therefore is :
1. Is there a way to create a new entry for every SalesExtra object ?
2. Is this the best method for creating a Notifications system? (i am using django 3.0 therefore many applications like django-notifications or django-activities-stream is not compatible.)
If you create a through table for the many to many relationship between SalesTask and SalesExtra, you can add the signal handler to this through table so that whenever a relationship is made between the 2 (a user has presumably been added) a notification will be made. If you save a SalesTask model form that has a multi select for the extras this will be triggered for each selection
class SalesTask(models.Model):
salesExtra = models.ManyToManyField('SalesExtra', through='SalesTaskExtra')
class SalesExtra(models.Model):
pass
class SalesTaskExtra(models.Model):
task = models.ForeignKey(SalesTask, on_delete=models.CASCADE)
extra = models.ForeignKey(SalesExtra, on_delete=models.CASCADE)
def CreateTaskNotification(sender, **kwargs):
if kwargs['created']:
Notifications.objects.create(
user=kwargs['instance'].extra.username,
message='You have been assigned a new task',
object_url=kwargs['instance'].task.get_absolute_url()
)
post_save.connect(CreateTaskNotification, sender=SalesTaskExtra)
Related
Edit: Rewrote Description for clarity
I have a "CustomUser" class that I got from an all-auth tutorial (Django All-Auth Tutorial) and I have user as a foreign key in each model, which works as intended, only showing records pertaining to the current logged in user to that specific user.
For example:
EDUCATION MODEL (Works Correctly Here)
class Education(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
EducationInstitutionName = models.CharField(verbose_name=_('Institution Name'), max_length=100, default=None)
EducationLevel = models.CharField(verbose_name=_('Education Level'), choices=EDUCATIONLEVEL, max_length=100, default=None)
EducationStartDate = models.DateField(verbose_name=_('Education Start Date'), default=None)
EducationEndDate = models.DateField(verbose_name=_('Education End Date'), default=None)
EducationCaoCode = models.CharField(choices=CAO_CODE, max_length=100, default=None)
EducationDesc = models.CharField(verbose_name=_('Education Description'), max_length=250, default=None)
def __str__(self):
return self.EducationInstitutionName
This works perfectly and I am achieving what is needed.
The issue arises when I have a table comprised of Foreign Keys which is the focal point of my application which takes the constituent parts of a CV and allows you to combine them to make a CV of interchangable sections.
CV MODEL (Only model I have which is composed of Foreign Keys)
class Cv(models.Model):
user = models.ForeignKey(
CustomUser,
on_delete=models.CASCADE,
)
CvName = models.CharField(verbose_name=_('CvName'), max_length=100, default=None)
CvEducation = models.ForeignKey(Education, on_delete=models.CASCADE)
CvSkills = models.ForeignKey(Skills, on_delete=models.CASCADE)
CvWorkExperience = models.ForeignKey(WorkExperience, on_delete=models.CASCADE)
def __str__(self):
return self.CvName
CV FORM
This is where I'm hitting the issue - I want to filter the fields to only show records of Education, Skills and Work Experience for the current user - at the moment the dropdown shows every record in existence for the Education, Skills and Work Experience models rather than those ONLY pertaining to the current logged in user.
class CvForm(forms.ModelForm):
class Meta:
model = Cv
fields = ('CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience')
# TRYING TO FILTER BY CURRENT USER
def __init__(self, user, *args, **kwargs):
super(CvForm, self).__init__(*args, **kwargs)
print(user)
self.fields['CvEducation'].queryset = Education.objects.filter(user=user)
self.fields['CvSkills'].queryset = Education.objects.filter(user=user)
self.fields['CvWorkExperience'].queryset = Education.objects.filter(user=user)
def save(self, commit=True):
cv = super(CvForm, self).save(commit=False)
cv.user = self.request.user
cv.CvName = self.cleaned_data['CvName']
cv.CvEducation = self.cleaned_data['CvEducation']
cv.CvSkills = self.cleaned_data['CvSkills']
cv.CvWorkExperience = self.cleaned_data['CvWorkExperience']
cv.save()
return cv
CV LIST VIEW (This - correctly - only shows completed CV records associated with the current logged in user)
class CvList(ListView):
model = Cv
fields = ['CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience']
success_url = reverse_lazy('Cv_list')
def get_queryset(self):
user_ids = CustomUser.objects.filter(username=self.request.user).values_list('id', flat=True)
if user_ids:
for uid in user_ids:
return Cv.objects.filter(user__id=uid)
else:
return Cv.objects.all()
CV CREATE VIEW
class CvCreate(CreateView):
model = Cv
fields = ['CvName', 'CvEducation', 'CvSkills', 'CvWorkExperience']
success_url = reverse_lazy('Cv_list')
def form_valid(self, form):
form.instance.user = self.request.user
return super(CvCreate, self).form_valid(form)
def get_queryset(self):
user_ids = CustomUser.objects.filter(username=self.request.user).values_list('id', flat=True)
if user_ids:
for uid in user_ids:
return Cv.objects.filter(user__id=uid)
else:
return Cv.objects.all()
This gives me...
Dropdown showing the whole Education table rather than filtering by Current User
In this image as I'm logged in as John Doe I should not be able to see the record - "James Edu" as that is associated with a different user James.
This occurs in all three of the fields I have with Foreign Keys - CvEducation, CvSkills and CvWorkExperience
Fixed eventually (in a very roundabout way):
FORMS INIT METHOD
def __init__(self, *args, **kwargs):
initial_arguments = kwargs.get('initial', None)
initial_arguments_list = list(initial_arguments.values())
user_id = initial_arguments_list[0]
super(CvForm, self).__init__(*args, **kwargs)
self.fields['CvEducation'].queryset = Education.objects.filter(user__id=user_id)
self.fields['CvSkills'].queryset = Skills.objects.filter(user__id=user_id)
self.fields['CvWorkExperience'].queryset = WorkExperience.objects.filter(user__id=user_id)
CREATE VIEW
class CvCreate(CreateView):
model = Cv
success_url = reverse_lazy('Cv_list')
form_class = CvForm
exclude = ['user']
def get_initial(self):
self.initial.update({ 'created_by': self.request.user.id })
return self.initial
I am learning DRF and I've now run into a problem that has stalled me for days. The app is a zumba class application and I'm using DRF to create an API on top of it. The part I am trying to build right now is the part where a user can add himself to a zumba class(so, he has to be able to update a manytomany field) What I'd like the API to do when the user register himself to the class(PUT, or PATCH), is to take his username, that we get from the authentication, and add him to "myusers" field. But since the PUT is empty, the API keeps complaining that "myusers" is required.
Is there a way to tell the API that "myusers" is not required in the PUT request since it is extracted from the authentication token? (If I manually create a PUT request with "myusers": [{"id": 9}] in the body, it works but I'd like to avoid adding that client side since the data passed is not even used.)
The serializer(all the reado-only fields are to make sure the user cannot update them):
class UserActivityClassesSerializer(serializers.ModelSerializer):
activitytypename = serializers.SlugRelatedField(source='activitytype', slug_field='name', read_only=True)
activitytype = ActivityTypeSerializer(read_only=True)
myusers = MyUsersSerializer(many=True) # serializers.HyperlinkedRelatedField(many=True, view_name='myusers-detail', read_only=True)
uri = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Activity
fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
'myusers',
]
read_only_fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
]
def get_uri(self, obj):
request = self.context.get('request')
return api_reverse("api-activities:classes-detail", kwargs={"id": obj.id}, request=request)
The view
class ActivityViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated]
queryset = Activity.objects.all()
lookup_field = 'id'
def get_serializer_class(self):
if self.request.user.is_staff or self.action == 'create':
return AdminActivityClassesSerializer
else:
return UserActivityClassesSerializer
def perform_update(self, serializer):
instance = self.get_object()
request = serializer.context['request']
auth_user = request.user
qs = instance.myusers.filter(id=auth_user.id)#we verify is the user is already registered, and if yes, we remove him
if qs:
print('delete')
instance.myusers.remove(auth_user)
return instance
else:
result = verify_valid_season_pass(auth_user, instance)
if result == 1:
print('add')
instance.myusers.add(auth_user.id)
else:
raise serializers.ValidationError("No valid seasonpass found.")
The model that gets updated:
class Activity(models.Model):
CLOSE = 0
OPEN = 1
ACTIVITY_STATUS_CHOICES = [
(CLOSE, 'Closed'),
(OPEN, 'Open'),
]
name = models.CharField('Activity Name', max_length=64, blank=False, null=False)
date = models.DateTimeField('Start Date & Time', blank=False, null=False)
activitytype = models.ForeignKey(ActivityType, blank=False, null=False, default=1, on_delete=models.DO_NOTHING)
status = models.IntegerField('Status', choices=ACTIVITY_STATUS_CHOICES, default=OPEN,) # 1 = open, 0 = closed
myusers = models.ManyToManyField("users.User")
def __str__(self):
return self.name
Any clues?
Have you tried
myusers = MyUsersSerializer(many=True, required=False)
We're required to have two separate forms for two different types of users. Call them Client and Provider. Client would be the parent, base user, while Provider is a sort of extension. At any point a Client could become a Provider as well, while still maintaining status and information as a Client. So a Provider has both permissions as a Client and as a Provider.
I'm new to Django. All we're trying to do is register either user type, but have a one to one relation between Provider and Client tables if a user registers as a Provider straight away.
The issue we're having is in the adapter, we think. A provider registers fine, but ends up in the users_user table with no entry in the generated users_provider table. Is it the way we're trying to save and relate these two entities in the database, or something else?
We're trying to utilize allauth for authentication and registration.
Our code:
models.py:
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
SEX = (
("M","MALE"),
("F","FEMALE"),
)
birthdate = models.DateField(_('Birth Date'), default=django.utils.timezone.now, blank=False)
sex = models.CharField(_('Sex'), choices=SEX, max_length=1, default="M")
isProvider = models.BooleanField(_('Provider'), default=False)
#Using User, not models.Model
class Provider(User):
HAS_BUSINESS = (
('YES','YES'),
('NO','NO'),
)
#Resolving asociation 1:1 to User
#NOTE: AUTH_USER_MODEL = users.User in setting
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
has_business = models.CharField(_('Do you have your own business?'),max_length=2, choices=HAS_BUSINESS, default='NO')
isProvider = True
our forms.py
class ProviderForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
Provider.isProvider = True
class Meta:
model = Provider
fields = '__all__'
exclude = GENERAL_EXCLUSIONS + [
'owner',
]
class ClientForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
class Meta:
model = User
fields = "__all__"
exclude = GENERAL_EXCLUSIONS
def is_active(self):
return False
def __init__(self, *args, **kwargs):
super(ClientForm, self).__init__(*args, **kwargs)
views.py:
class ProviderRegisterView(SignupView):
template_name = 'account/form_provider.html'
form_class = ProviderForm
redirect_field_name = 'next'
view_name = 'registerprovider'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ProviderRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerprovider = ProviderRegisterView.as_view()
#View para el formulario de registro de usuarios clientes
class ClientRegisterView(SignupView):
template_name = 'account/form_client.html'
form_class = ClientForm
redirect_field_name = 'next'
view_name = 'registerclient'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ClienteRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerclient = ClienteRegisterView.as_view()
finally, our adapter.py:
#Per allauth documentation, settings changed:
#ACCOUNT_ADAPTER = 'projectname.users.adapters.RegisterUserAdapter'
class RegisterUserAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
data = form.cleaned_data
user.first_name = data['name']
user.last_name = data['lastname']
#Saving Client info
user.sex = data['sex']
user.birthdate = data['birthdate']
#Normal allauth saves
user.username = data['username']
user.email = data['email']
if user.isProvider:
p = Provider()
p.owner = user
p.has_business = data['has_business']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
#Save user
user.save()
#If it's also a Provider, save the Provider
if user.isProvider:
p.save()
return user
Any help or tips would be greatly appreciated. If I left something out, please let me know. I'm not sure if the problem is in the model itself, the way we represent the form, or the adapter. The way it stands, it doesn't matter what form we use, it's always saved as the base User table (our Client) and the Provider table never gets information saved to it.
With Django's new custom user model, only one user model can be set as settings.AUTH_USER_MODEL. In your example, you can set this to your User model.
Then for the optional provider data, create a separate model that is referenced by OneToOneField from your User model.
class User(AbstractUser):
...
provider = models.OneToOneField(Provider, null=True)
class Provider(models.Model):
...
This is the easiest way to work with multiple user types in Django, given the AUTH_USER_MODEL constraint.
Also, it's best to only subclass abstract models, otherwise you get multitable inheritance which results in hidden implied JOINs, degrading performance.
Finally, you can create the Provider object in your custom form's form.is_valid() method and assign user.provider = provider.
In my code CreateNoticeForm is working fine and all data gets saved perfectly except
many to many field which is tags in my notice model.Although it is working on admin site and gets saved.
here is my code
models.py
from django.db import models
from django.contrib.auth.models import User
from wysihtml5.fields import Wysihtml5TextField
# Create your models here.
class Tag(models.Model):
# For notice tags
name = models.CharField(max_length=70, unique=True)
def __str__(self):
return self.name
class Notice(models.Model):
# Notice Store
headline = models.CharField(max_length=140)
description = Wysihtml5TextField()
file_name = models.FileField(upload_to='static/noticefiles/', blank=True)
created_by = models.ForeignKey(User)
fors = models.CharField(max_length=1, choices=(('F','Faculty'),('S','Student'),) )
last_date = models.DateField()
tags = models.ManyToManyField(Tag, blank=True)
post_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
def __str__(self):
return self.headline
forms,py
FORS_CHOICES = (('F','Faculty'),('S','Student'))
class CreateNoticeForm(ModelForm):
fors = forms.ChoiceField(label="Related To",
choices=FORS_CHOICES,
)
class Meta:
model = Notice
fields = ('headline', 'description',
'fors', 'last_date', 'tags','file_name')
widgets = {
'description': Wysihtml5BootstrapWidget(),
'last_date': SelectDateWidget()
}
def __init__(self, *args, **kwargs):
super(CreateNoticeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'create_notice_form_id'
self.helper.form_class = 'form-horizontal'
self.helper.form_method = 'post'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-8'
self.helper.layout = Layout(
Fieldset('Create Notice',
'headline',
'description',
Field('fors', label='Audience'),
MultiWidgetField('last_date',
attrs=(
{'style': 'width: 33.33%; display: inline-block;'}
)
),
'tags',
'file_name',
FormActions(
Submit('save', 'Create Notice',
css_class='btn-warning col-lg-offset-2'),
),
),
views.py
def create_notice(request):
context = RequestContext(request)
posted = False
if request.method=='POST':
create_notice_form = CreateNoticeForm(data=request.POST, files=request.FILES)
if create_notice_form.is_valid():
cnf = create_notice_form.save(commit=False)
cnf.created_by = request.user
cnf.save()
posted = True
else:
print(create_notice_form.errors)
else:
create_notice_form = CreateNoticeForm()
return render_to_response('notices/createnotice1.html',
{'create_notice_form': create_notice_form,
'posted': posted,},
context)
You have to call save_m2m():
cnf = create_notice_form.save(commit=False)
cnf.created_by = request.user
cnf.save()
create_notice_form.save_m2m()
Excerpt from the documentation:
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
You have to Call the following after validating the form:-
if create_notice_form.is_valid():
parent = create_notice_form.save(commit=False)
parent.save()
create_notice_form.save_m2m()
I hope this will help you
models.py
TITLE = (
('Classroom', 'Classroom'),
('Playground', 'Playground'),
('Staff Room','Staff Room'),
)
class Location(models.Model):
user = models.ForeignKey(User,null=True)
title = models.CharField('Incident Type', max_length=200,default=TITLE)
parent_location_id = models.CharField('Parent Location', max_length=100, null=True, blank=True)
is_active = models.BooleanField('Is Active', default=True)
def location_title(sender, instance, created, **kwargs):
if instance.is_superuser and not instance.location.is_active:
instance.location.is_active=True
instance.location.save()
post_save.connect(location_title, sender=User)
I want to insert the default data into database with certain conditions.This should happen while creating superuser via manage.py createsuperuser comment.
I don't know it is possible with django,but it is the requirement.I tried with the above code.I am getting the error "AttributeError: 'User' object has no attribute 'location'
" while creating superuser.
The sample what i required is given below
Try this function as signal handler:
def location_title(sender, instance, created, **kwargs):
# Don't fire up on updates.
if not created:
return
# Only handle new superusers.
if not instance.is_superuser or not instance.is_active:
return
# Create a `Location` entry for new superuser.
l = Location(user_id=instance.pk)
l.save()
post_save.connect(location_title, sender=User)
Adding choices to model field:
Django CharField has a named argument choices that allows you to give the end user a list of possible values, and a proper validation of them in forms. The format of the iterable is as follows <internal_value>, <display_value>. Once a field has been passed a choices argument, you can access display value connected to it's internal value with instance.get_<field_name>_display() method.
The choices iterable could look like this:
class Location(models.Model):
class Title:
CLASSROOM = 'classroom'
PLAYGROUND = 'playground'
STAFF_ROOM = 'staff_room'
TITLE_CHOICES = (
(Title.CLASSROOM, 'Classroom'),
(Title.PLAYGROUND, 'Playground'),
(Title.STAFF_ROOM, 'Staff Room'),
)
user = models.ForeignKey(User,null=True)
title = models.CharField('Incident Type', max_length=200,choices=TITLE_CHOICES,default=Title.CLASSROOM)
parent_location_id = models.CharField('Parent Location', max_length=100, null=True, blank=True)
is_active = models.BooleanField('Is Active', default=True)
The final solution is the following:
class Location(models.Model):
class Title:
CLASSROOM = 'classroom'
PLAYGROUND = 'playground'
STAFF_ROOM = 'staff_room'
BASE_LOCATION = Title.CLASSROOM
TITLE_CHOICES = (
(Title.CLASSROOM, 'Classroom'),
(Title.PLAYGROUND, 'Playground'),
(Title.STAFF_ROOM, 'Staff Room'),
)
user = models.ForeignKey(User,null=True)
title = models.CharField('Incident Type', max_length=200,choices=TITLE_CHOICES,default=Title.CLASSROOM)
parent_location_id = models.CharField('Parent Location', max_length=100, null=True, blank=True)
is_active = models.BooleanField('Is Active', default=True)
def location_title(sender, instance, created, **kwargs):
# Don't fire up on updates.
if not created:
return
# Only handle new superusers.
if not instance.is_superuser or not instance.is_active:
return
# Create a `Location` entry for new superuser.
base = Location(user_id=instance.pk, title=Location.BASE_LOCATION)
base.save()
for value, _ in Location.TITLE_CHOICES:
if value == Location.BASE_LOCATION:
continue
l = Location(user_id=instance.pk, title=value, parent_location_id=base.pk)
l.save()
post_save.connect(location_title, sender=User)
When you create the user or superuser the model instance is created but it does not have corresponding location row. Hence, accessing instance.location.is_active gives you the error.
You can update the signal handler to create the location instance first and then set appropriate attributes. As below:
def location_title(sender, instance, created, **kwargs):
#also check for created flag
if created and instance.is_superuser:
location = Location(user=instance)
location.is_active=True
location.save()
post_save.connect(location_title, sender=User)
If you want to choices for title field you can define that in field. Your definition of the title field is not correct, change it to
title = models.CharField('Incident Type', max_length=200, choices=TITLE,
default='Classroom')
You are incorrectly using detfault=TITLE instead of choices=TITLE.