Django join button to goal - django

I try to create changing button after user join to Goal.
I have work buttons, but i don't know how to change this in template.
I try to do this with boolean field, when user click "Join", boolean field change to True, and then in template I try:
{% if goal.joined %}
<Delete join>
{% else %}
<join to goal>
{% endif %}
Models:
class Goal(models.Model, Activity):
title = models.CharField(max_length=255, verbose_name='Tytuł')
image = models.ImageField(upload_to='goals', verbose_name='Tło')
body = HTMLField(verbose_name='Treść')
tags = TaggableManager()
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = AutoSlugField(populate_from='title')
def __str__(self):
return self.title
class Meta:
verbose_name = 'Cele'
ordering = ['-created_at']
def get_absolute_url(self):
return reverse('goaldetail', args=[str(self.slug)])
#property
def activity_actor_attr(self):
return self.author
def add_user_to_list_of_attendees(self, user):
registration = Joined.objects.create(user = user,
goal = self,
created_at = timezone.now())
def remove_user_from_list_of_attendees(self, user):
registration = Joined.objects.get(user = user, goal = self)
registration.delete()
class Joined(models.Model, Activity):
goal = models.ForeignKey(Goal, on_delete=models.CASCADE, related_name='joined')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='joined_users')
created_at = models.DateTimeField(auto_now_add=True)
joined = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self.id is None and self.created_at is None:
self.created_at = datetime.datetime.now()
self.joined = True
super(Joined, self).save(*args, **kwargs)
#property
def activity_actor_attr(self):
return self.user
Views:
def joined_add(request, pk):
this_goal = Goal.objects.get(pk=pk)
this_goal.add_user_to_list_of_attendees(user=request.user)
return redirect('goaldetail', slug=this_goal.slug)
def joined_delete(request, pk):
this_goal = Goal.objects.get(pk=pk)
this_goal.remove_user_from_list_of_attendees(request.user)
return redirect('goaldetail', slug=this_goal.slug)
def goaldetail(request, slug):
goal = get_object_or_404(Goal, slug=slug)
return render(request, 'goals/detail.html',
{'goal': goal})
Path:
path('joined/<int:pk>', views.joined_add, name='joined_add'),
path('joined-delete/<int:pk>', views.joined_delete, name='joined_delete'),
Create and delete works perfect, but on template this button don't change. I try in many ways and don't know how to do this.

Related

Custom Permissions in Django Rest Framework

I have this get View that I want to check IsOwner Permission.
Permission Class
class IsOwnerVendor(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
print(f"Vendor Email:{obj.vendor_id.email}")
print(f"Loggon user:{obj.vendor_id.email}" )
return obj.vendor_id.email == request.user
this is my object Model
class Menu(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500)
price = models.FloatField()
quantity = models.IntegerField(default=1)
menu_cat = models.CharField(choices=MENU_CAT, max_length=5)
date_created = models.DateTimeField(auto_now_add=True)
last_edited = models.DateTimeField(auto_now=True)
vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE)
is_recurring = models.BooleanField(default=False)
recurring_freq = models.IntegerField(default=1)
Vendor Model
class Vendor(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
business_name = models.CharField(max_length=100)
email = models.EmailField()
phone_number = PhoneField(help_text='Vendor phone number')
registered_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.business_name
and this is my view
class MenuDetailView(generics.GenericAPIView):
permission_classes = [IsOwnerVendor | IsOwnerVendor]
def get_serializer_class(self):
if self.request.method == 'PUT':
return MenuUpdateSerializer
elif self.request.method == 'GET':
return MenuListSerializer
else:
return MenuListSerializer
def get_object(self, pk):
try:
obj = Menu.objects.get(pk=pk, )
self.check_object_permissions(self.request, obj)
return obj
except Menu.DoesNotExist:
raise Http404
#method_permission_classes((IsOwnerVendor,))
def get(self, request, pk, format=None):
my_menu = self.get_object(pk=pk)
menu_serializer = MenuListSerializer(my_menu)
return Response(menu_serializer.data, status=status.HTTP_200_OK)
When I try to access the view, I always get the error below
{
"detail": "You do not have permission to perform this action."
}
I have read the DRF doc and I still cannot pinpoint where my issue lies.
I also printed the Permission checkers on the console and saw that it was supposed to return true.
The main point is you should let has_object_permission method return True, you can try:
return obj.vendor_id.email == request.user.email or
return obj.vendor_id.user == request.user, if this still not work, you can print log or set breakpoint to see why this method return False.Remember to restart you localserver before you next test.

How to get the id using a get_context_data?

I would like to get the total amount of followers attached to the models using in models :
class Project(models.Model):
owner = models.ForeignKey(User, related_name='project_created_by', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.TextField(max_length=150, blank=True, null=True)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followers', blank=True)
created = models.DateTimeField(auto_now_add=True)
last_modefied = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Here is the class
class ProjectListView(ListView):
template_name = 'projectmanagement/project.html'
context_object_name = 'projects'
def get_queryset(self):
queryset = Project.objects.filter(owner=self.request.user).order_by("name")
return queryset
def get_context_data(self, *args, **kwargs):
context = super(ProjectListView, self).get_context_data(*args, **kwargs)
project = Project.objects.get(pk=12) <-- HERE -->
context['followers'] = project.followers.filter(followers=project).count()
return context
You can .annotate(..) [Django-doc] the queryset of your Product with the number of followers:
from django.db.models import Count
class ProjectListView(ListView):
model = Project
template_name = 'projectmanagement/project.html'
context_object_name = 'projects'
def get_queryset(self):
return super().get_queryset().annotate(
nfollowers=Count('followers')
).filter(
owner=self.request.user
).order_by('name')
Now all projects in the context data will have an extra attribute nfollowers with the number of followers.
You can thus render this for example with:
{% for project in projects %}
{{ project.name }}, followers: {{ project.nfollowers }}<br>
{% endfor %}

Voting on a model

I'm having trouble incrementing the vote_score attribute of my model every time it is voted on. This is my model:
# idea model
class Idea(models.Model):
User = ('accounts.User')
creator = models.ForeignKey(User, related_name='ideas', on_delete=models.PROTECT)
title = models.CharField(max_length=100, null=True, blank=True)
vote_score = models.BigIntegerField(default=0, null=True, blank=True)
votes = VotableManager()
#vote model
class Vote(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
create_at = models.DateTimeField(auto_now_add=True)
vote = models.NullBooleanField()
objects = VoteManager()
class Meta:
unique_together = ('user', 'content_type', 'object_id')
#classmethod
def votes_for(cls, model, instance=None):
ct = ContentType.objects.get_for_model(model)
kwargs = {
"content_type": ct
}
if instance is not None:
kwargs["object_id"] = instance.pk
return cls.objects.filter(**kwargs)
#manager
_VotableManager(models.Manager):
def __init__(self, through, model, instance, field_name='votes', extra_field='vote_score'):
self.through = through
self.model = model
self.instance = instance
self.field_name = field_name
self.extra_field = extra_field
self.name = model.title
#instance_required
def up(self, user, vote):
with transaction.atomic():
if self.through.objects.filter(user=user, content_object=self.instance).exists():
c_type = ContentType.objects.get_for_model(self.instance)
vote_obj = self.through.objects.get(user=user, object_id=self.instance.id, content_type=c_type)
vote_obj.vote = vote
vote_obj.save()
self.instance.save()
else:
self.through(user=user, content_object=self.instance, vote=vote).save()
if self.extra_field:
setattr(self.instance, self.extra_field, F(self.extra_field)+1)
self.instance.save()
My goal is to have it so when the idea is created the creator automatically counts as 1 vote toward it so that falls under this view:
# idea create view
class IdeaCreateView(LoginRequiredMixin, CreateView):
model = Idea
form_class = IdeaCreateForm
template_name = 'idea_create.html'
success_url = 'success'
def dispatch(self, *args, **kwargs):
self.user = get_object_or_404(User, pk=kwargs['pk'])
return super(IdeaCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
idea = form.save(commit=False)
idea.creator = self.request.user
idea.save()
idea.votes.up(user=idea.creator, vote=True)
idea.vote_score += 1
return HttpResponseRedirect('success')
And then to allow other users to vote on it as well, which falls under this view:
#vote view
class IdeaVoteView(LoginRequiredMixin, CreateView):
form_class = VoteForm
required_fields = ('action',)
template_name = 'vote_confirm.html'
success_url = 'success'
def form_valid(self, form):
obj = Idea.objects.get(pk=self.kwargs['pk'])
user = self.request.user
user_id = self.request.user.pk
object_id = obj.pk
content_type_id = 10
form_class = VoteForm
self.vote = form.save(commit=False)
self.vote.user_id = user_id
self.vote.object_id = object_id
self.vote.content_type_id = content_type_id
if obj.votes.exists(user_id):
return HttpResponseRedirect('failure')
else:
self.vote.save()
obj.votes.up(user_id)
return HttpResponseRedirect('success')
def get_object(self, queryset=None):
obj = Idea.objects.get(pk=self.kwargs['pk'])
voters = obj.get_voters()
return voters
Why doesn't setting the extra_field attribute on the manager take care of this and what's going wrong in the views?
Wow, great news... After talking to myself for hours I figured it out. I created this model function:
def vote_up(self):
self.vote_score += 1
self.save()
The problem was I wasn't calling the save() method on this function so the increment wasn't getting saved! Really simple, but I glossed right over it. Anyway, maybe answering the question will help someone.

Django Rest Framework - display prepopulated slug field

I have a slug field for a model that I would like returned in the object representation but NOT as part of the form input in the browsable API. It is generated by a slugify method on the model.
When I mark it as read only in it's ModelSerializer by adding it to Meta using read_only_fields=('slug',) trying to add new fields in the browseable api form yields "This field is required."
The serializer for reference is below:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
slug = serializers.SlugField(read_only=True, required=False)
def to_representation(self, obj):
self.fields['children'] = CategorySerializer(obj, many=True, read_only=True)
return super(CategorySerializer, self).to_representation(obj)
class Meta:
model = Category
fields = ('pk', 'url', 'title', 'slug', 'parent', 'children', 'active', 'icon')
read_only_fields = ('children','slug',)
What is a simple solution to show the field in the representation and not the browseable api form given the above?
For reference, here is my model:
#python_2_unicode_compatible
class CategoryBase(mptt_models.MPTTModel):
parent = mptt_fields.TreeForeignKey( 'self', blank=True, null=True, related_name='children', verbose_name=_('parent'))
title = models.CharField(max_length=100, verbose_name=_('name'))
slug = models.SlugField(verbose_name=_('slug'), null=True)
active = models.BooleanField(default=True, verbose_name=_('active'))
objects = CategoryManager()
tree = TreeManager()
def save(self, *args, **kwargs):
"""
While you can activate an item without activating its descendants,
It doesn't make sense that you can deactivate an item and have its
decendants remain active.
"""
if not self.slug:
self.slug = slugify(self.title)
super(CategoryBase, self).save(*args, **kwargs)
if not self.active:
for item in self.get_descendants():
if item.active != self.active:
item.active = self.active
item.save()
def __str__(self):
ancestors = self.get_ancestors()
return ' > '.join([force_text(i.title) for i in ancestors] + [self.title, ])
class Meta:
abstract = True
unique_together = ('parent', 'slug')
ordering = ('tree_id', 'lft')
class MPTTMeta:
order_insertion_by = 'title'
class Category(CategoryBase):
icon = IconField(null=True, blank=True)
order = models.IntegerField(default=0)
#property
def short_title(self):
return self.title
def get_absolute_url(self):
"""Return a path"""
from django.core.urlresolvers import NoReverseMatch
try:
prefix = reverse('categories_tree_list')
except NoReverseMatch:
prefix = '/'
ancestors = list(self.get_ancestors()) + [self, ]
return prefix + '/'.join([force_text(i.slug) for i in ancestors]) + '/'
def save(self, *args, **kwargs):
super(Category, self).save(*args, **kwargs)
class Meta(CategoryBase.Meta):
verbose_name = _('category')
verbose_name_plural = _('categories')

Django formsets

I'm making a survey site with django. I am pretty newbie with django so I apologize in advance if I can not explain well. My question focuses on the following models:
class SurveyType(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Question(models.Model):
ANSWERTYPE_CHOICES = (
(u'T', u'Text'),
(u'R', u'Range'),
(u'M', u'Media'),
)
question = models.CharField(max_length=200)
surveytype = models.ForeignKey(SurveyType)
answertype = models.CharField(max_length=1, choices=ANSWERTYPE_CHOICES)
order = models.IntegerField(default=0)
def __unicode__(self):
return self.question
class Survey(models.Model):
course = models.ForeignKey(Course)
surveytype = models.ForeignKey(SurveyType)
def __unicode__(self):
return u"%s %s" % (self.course, self.surveytype)
class Answer(models.Model):
answer = models.CharField(max_length=400)
survey = models.ForeignKey(Survey)
question = models.ForeignKey(Question)
def __unicode__(self):
return self.answer
Django receives survey id. With the survey id it gets the surveytype and shows questions that must be displayed.
def survey(request, survey_id):
survey_data = get_object_or_404(Survey, pk=survey_id)
survey_type = survey_data.surveytype
questions = Question.objects.all().filter(surveytype = survey_type).order_by('order')
I have read the django documentation about formsets but I don't understand what I have to write in forms.py, so I can't call the form in views.py to render de form in the template that shows the questions and write the answers in the answer model.
Thanks in advance and sorry for my english.
Solved using modelforms and a foor loop.
Models.py
class Question(models.Model):
question = models.CharField(max_length=200)
surveytype = models.ForeignKey(SurveyType)
answertype = models.ForeignKey(ContentType,
limit_choices_to = Q(name='text answer', app_label='surveys')| \
Q(name='media answer', app_label='surveys')| \
Q(name='range answer', app_label='surveys'))
class RangeAnswer(models.Model):
answer = models.IntegerField(max_length=1, choices=CHOICES_RANGE, default=0)
def __unicode__(self):
return u'%s'%(self.answer)
class TextAnswer(models.Model):
answer= models.CharField(max_length=200)
def __unicode__(self):
return u'%s'%(self.answer)
class MediaAnswer(models.Model):
answer= models.ForeignKey(Media)
def __unicode__(self):
return u'%s'%(self.answer)
Forms.py
class RangeAnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RangeAnswerForm, self).__init__(*args, **kwargs)
self.fields['answer'].label = "Mi valoración"
class Meta:
model = RangeAnswer
widgets = {
'answer': forms.RadioSelect(renderer=RadioRenderer)
}
RangeAnswer.form = RangeAnswerForm
class MediaAnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MediaAnswerForm, self).__init__(*args, **kwargs)
self.fields['answer'].label = "Medio"
class Meta:
model = MediaAnswer
MediaAnswer.form= MediaAnswerForm
class TextAnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TextAnswerForm, self).__init__(*args, **kwargs)
self.fields['answer'].label = "Respuesta"
class Meta:
model = TextAnswer
TextAnswer.form = TextAnswerForm
Views.py
for q in questions :
q.form = q.answertype.model_class().form(prefix="%s"%q.id)
Have you import the form to your views.py?
After that you just have to create a view that will pass the form to a template.
For example in the views.py
def your_form(request):
form = RegisterForm()
return render_to_response('your_template.html', {'form': form}, RequestContext(request))
and then in your template you can render the form simply by writing
{{ form }}