I'm trying to customize this tutorial for creating click counter using Kombu and Celery in Django. The tutorial explains how to do it for url's, but what I want to do is to create a Click model, define Posts as ForeignKey field and then each time someone view that post, I'd call increment clicks for that post.
I had two problems:
first one is where should I put the function call in views, since I'm using generic views (see below) ?
second one is how should I work with message? Did I pass the object write to the function?
models.py
class ClickManager(models.Manager):
def increment_clicks(self, for_url, increment_by=1):
click, created = self.get_or_create(url=for_url, defaults={"click_count": increment_by})
if not created:
click.click_count += increment_by
click.save()
return click.click_count
class Click(models.Model):
obj = models.ForeignKey(Post)
click_count = models.PositiveIntegerField(_(u"click_count"), default=0)
objects = ClickManager()
def __unicode__(self):
return self.obj
messaging.py
def send_increment_clicks(obj):
connection = establish_connection()
publisher = Publisher(connection=connection, exchange="clicks", routing_key="increment_click", exchange_type="direct")
publisher.send(obj)
publisher.close()
connection.close()
def process_clicks():
connection = establish_connection()
consumer = Consumer(connection=connection, queue="clicks", exchange="clicks", routing_key="increment_click", exchange_type="direct")
clicks_for_url = {}
message_for_url = {}
for message in consumer.iterqueue():
obj = message.body
clicks_for_url[obj]= clicks_for_url.get(obj, 0) + 1
if obj in messages_for_url:
messages_for_url[obj].append(message)
else:
messages_for_url[obj] = [message]
for obj, click_count in clicks_for_url.items():
Click.objects.increment_click(obj, click_count)
[message.ack() for message in messages_for_url[obj]]
consumer.close()
connection.close()
views.py
class LinkDetailView(FormMixin, DetailView):
models = Post
queryset = Post.objects.all()
"""DON'T KNOW HOW TO PASS SELF.OBJECT TO THE FUNCTION """
send_increment_clicks[self.object]
def get_success_url(self):
...
def get_context_data(self, **kwargs):
...
Related
I am creating a simple online shop app so when you want to buy an item, a button click leads you to the charge api. (ex. item 2 will direct to /api/charge/2)
urls.py
from django.urls import path
from gallery.views import ClothingView
from gallery.api.views import ListClothes
from gallery.api.views import ChargeView
urlpatterns = [
path('api/<int:num>/', ListClothes.as_view()),
path('api/charge/<int:num>', ChargeView.as_view(), name='charge_api'),
]
views.py
class ChargeView(ListCreateAPIView):
serializer_class = ChargeSerializer
count = 0
def get_queryset(self):
a = ClothingModel.objects.filter(id=self.kwargs['num']).first()
net_price = int(float(a.full_price) * 100)
if float(a.discount) > 0.00:
net_price = int(net_price * (1 - (float(a.discount) / 100)))
self.count += 1
print(self.count)
if self.count == 1:
stripe.api_key = settings.API_KEY
charge_rtn_body = stripe.Charge.create( # Create charge object
amount=net_price,
currency="usd",
source="tok_visa", # obtained with Stripe.js
description= "[Stripe charge] " + a.description.upper()
)
return ClothingModel.objects.filter(id=self.kwargs['num'])
serializers.py
class ChargeSerializer(serializers.ModelSerializer):
class Meta:
model = ClothingModel
fields = ('full_price', 'discount', 'description')
I am creating a charge object for Stripe (payment method) each time the api gets called, but dependent on the clothing item id. So to handle this I use self.kwargs on get_queryset() to link to a clothing item. When I view the charges on my Stripe dashboard after a single click, multiple charges come at once (4 at a time). I have hard coded the if self.count == 1: as a work around but know that is not good practice. Is there a reason for these multiple calls in get_queryset() per single request and how can I cleanly implement? Thank you.
Objects should be created only in POST requests. get_queryset is called everytime a view is invoked (even for GET requests). So the object create should be moved inside the view function. The dynamic part of the URL is accessible from the view function as present here - https://docs.djangoproject.com/en/2.1/topics/http/urls/#example
class ChargeView(ListCreateAPIView):
def post(self, request, num):
charge = ClothingModel.objects.get(id=num)
from django.conf import settings # new
from django.views.generic.base import TemplateView
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
class HomePageView(TemplateView):
template_name = 'stripe.html'
def get_context_data(self, **kwargs): # new
context = super().get_context_data(**kwargs)
context['key'] = settings.STRIPE_PUBLISHABLE_KEY
return context
def charge(request): # new
if request.method == 'POST':
charge = stripe.Charge.create(amount=5000,currency='ind',description='A Django charge',
source=request.POST['stripeToken'])
return render(request, 'charge.html')
class Biochemical_analysis_of_blood(CreateView):
model = BiochemicalAnalysisOfBlood
form_class = BiochemicalAnalysisOfBloodForm
template_name = "biochemical_analysis_of_blood.html"
success_url = reverse_lazy("patients")
def get_context_data(self, **kwargs):
context = super(Biochemical_analysis_of_blood, self).get_context_data(**kwargs)
patient = Patient.objects.get(id=1)
context["patient"] = patient
return context
def post(self, request, *args, **kwargs):
analysis = Analyzes()
sid = transaction.savepoint()
analysis.name = request.POST["name"]
analysis.patient_id = Patient.objects.get(id=1)
analysis.who_send = request.POST["who_send"]
analysis.who_is_doctor = request.POST["who_is_doctor"]
analysis.lab_user_id = Doctor.objects.get(id=request.POST["lab_user_id"])
analysis.additional_lab_user = request.POST["lab_user_add"]
analysis.date = '2017-06-18'
analysis.type = 3
analysis.date_analysis = '2017-06-18'
analysis.save()
return super(Biochemical_analysis_of_blood, self).post(request, *args, **kwargs)
I have next algorithm:
Render BiochemicalAnalysisOfBloodForm to the user
When he fills fields and presses button "save" I create a new instance of Analyzes() and fill it programmatically and when in the post method I call super().post() then users data will be written to the model BiochemicalAnalysisOfBlood automatically? But I have next error:
NOT NULL constraint failed:
laboratory_biochemicalanalysisofblood.analysis_id
How can I in hand mode add to the model to the field "analysis" the early created instance of Analyzes()? I don't understand this class to the end where I can find information about all it's opportunities
The main part of your algorithm should reside in your form, because you want to pass the analysis_id to the instance being saved
class BiochemicalAnalysisOfBloodForm(ModelForm):
def save(self, commit=True):
analysis = Analyzes()
sid = transaction.savepoint()
analysis.name = self.data["name"]
analysis.patient_id = Patient.objects.get(id=1)
analysis.who_send = self.data["who_send"]
analysis.who_is_doctor = self.data["who_is_doctor"]
analysis.lab_user_id = Doctor.objects.get(id=self.data["lab_user_id"])
analysis.additional_lab_user = self.data["lab_user_add"]
analysis.date = '2017-06-18'
analysis.type = 3
analysis.date_analysis = '2017-06-18'
analysis.save()
# Your analysis is created, attach it to the form instance object
self.instance.analysis_id = analysis.id
return super().save(commit)
Before doing the super().post you can modify the request.POST data to include your analysis id:
request.POST['analysis_id'] = analysis.id
that might help.
Also note that if the form validation fails in super().post, you will still have created an Analysis object which might not be useful. You could use override the form_invalid method of the CreateView to handle this.
Am using codecoverage and it complains that I have 2 functions in 2 different class based views that are too similar.
Attached is the codecoverage error
Below are the code that were highlighted:
class PalletContentPickup(APIView):
"""
Picking up a pallet content to transfer to exiting pallet content
"""
def put(self, request, pk):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
Transfer.validate_if_can_pickup_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
class PalletContentPutback(APIView):
"""
Put back pallet content to an approved pallet
"""
def put(self, request, pk):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
Transfer.validate_if_can_putback_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
I read about strategy pattern in Python
Not sure if I should apply strategy pattern here and if so, how? Because the example in the url still does not help me realise exactly how to perform strategy pattern here.
As i see it there is only one line of difference between the two classes. so you can keep it really simple by
class PalletContent(object):
"""
Put back pallet content to an approved pallet
"""
def do_action(self, request, pk, action):
count = request.data['count']
pallet_id = request.data['pallet_id']
from_pallet_content = QuickFind.get_pallet_content_or_404(pallet_content_id=pk)
to_pallet = QuickFind.get_pallet_or_404(pallet_id=pallet_id)
if action == 'putback':
Transfer.validate_if_can_putback_pallet_content(from_pallet_content, to_pallet, request.user)
else:
Transfer.validate_if_can_pickup_pallet_content(from_pallet_content, to_pallet, request.user)
to_pallet_content = QuickFind.get_or_create_pallet_content(pallet=to_pallet, product=from_pallet_content.product)
ExitFormHelper.create_exit_form_line_item_on_pallet_content_if_no_exit_form_line(to_pallet_content, request.user)
Transfer.previous_pallet_content_to_new_pallet_content(from_pallet_content, to_pallet_content, count)
serializer = PalletSerializer(from_pallet_content.pallet)
return Response({"data": serializer.data}, status=status.HTTP_202_ACCEPTED)
class PalletContentPickup(APIView, PalletContent):
def put(self,request,pk):
self.do_action(request,pk,'pickup')
class PalletContentPutback(APIView, PalletContent):
def put(self,request,pk):
self.do_action(request,pk,'putback')
You are saving only a few lines of code but it may be worth it when it comes to maintenance. At the same time your validate methods do not seem to return anything. Are they raising exceptions?
I have a Django template that has data from a few different model types combining to make it. A dashboard if you will. And each of those has an edit form.
Is it best to process all those forms in one view as they are posted back to the same place and differentiating between them by a unique field like below?
Or if having lots of different dedicated avenues is the way forward? Thanks for any guidance
class ProjectDetail(DetailView):
template_name = 'project/view.html'
def get_object(self):
try:
return Project.objects.filter(brief__slug=self.kwargs['slug']).filter(team=get_user_team(self.request)).first()
# add loop to allow multiple teams to work on the same brief (project)
except Exception as e:
project_error = '%s (%s)' % (e.message, type(e))
messages.error(self.request, 'OH NO! %s' % project_error)
return redirect('home')
def get_context_data(self, **kwargs):
project = self.get_object()
context = dict()
context['project'] = project
context['milestone_form'] = MilestoneForm(initial={'project': project})
context['view'] = self
return context
def post(self, request, *args, **kwargs):
# get the context for the page
context = self.get_context_data()
try:
# switch for each of the form types on the team profile page (shown if member)
if 'milestone_form_submit' in request.POST:
project=self.get_object()
# set date arbitrarily to half way to brief deadline
brief = Brief.objects.get(project=project)
last_milestone = self.milestones().last()
milestone_del_date = last_milestone.del_date + timedelta(days=7)
new_milestone = Milestone(
project=project,
title_text=request.POST.get('title_text'),
del_date=milestone_del_date,
)
try:
new_milestone.save()
messages.success(self.request, "Excellent! New delivery popped on the bottom of the list")
except Exception as e:
# pass the erroring form back in the context if not
form = MilestoneForm(request.POST)
context['milestone_form'] = form
messages.error(self.request, "OH NO! Deadline didn't save. Be a sport and check what you entered")
elif 'milestone-edit-date-form-submit' in request.POST:
# get object from db
milestone = Milestone.objects.get(pk=request.POST['id'])
# update del_date field sent
milestone.del_date = request.POST['del_date']
# save back to db
milestone.save()
messages.success(self.request, "Updated that delivery right there!")
elif ...
except Exception as e:
messages.error(self.request, "OH NO! Deadline didn't save. Be a sport and check what you entered")
return render(request, self.template_name, context)
You can use mixins in order to solve your problem.
Example from the gist https://gist.github.com/michelts/1029336
class MultipleFormsMixin(FormMixin):
"""
A mixin that provides a way to show and handle several forms in a
request.
"""
form_classes = {} # set the form classes as a mapping
def get_form_classes(self):
return self.form_classes
def get_forms(self, form_classes):
return dict([(key, klass(**self.get_form_kwargs())) \
for key, klass in form_classes.items()])
def forms_valid(self, forms):
return super(MultipleFormsMixin, self).form_valid(forms)
def forms_invalid(self, forms):
return self.render_to_response(self.get_context_data(forms=forms))
As you can see, when you inherit from this class, you can handle multiple forms simultaneously. Look at the gist's code and adapt it to your problem.
Look at this answer
I am trying to amend my get_success_url so that if any kwargs have been passed to it, I can build the returned url using them.
Heres what I have so far:
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
self.get_success_url(idnumber = model_b.pk)
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
self.archive_calc(object_id)
#save
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
So far this just gives a keyerror detailing 'idnumber'.
I have printed kwargs['idnumber'] and it returns the pk as expected however I just cant seem to see where I am going wrong with this.
Thanks in advance.
form_valid should return a HttpResponseRedirect https://github.com/django/django/blob/master/django/views/generic/edit.py#L57 which in your case, you never do. I dont know if you have any code after #save, but take a look at the comments I made in your code
class CalcUpdate(SuccessMessageMixin, UpdateView):
model = Calc
template_name = 'calc/cru_template.html'
form_class = CalcForm
def archive_calc(self, object_id):
model_a = Calc.objects.get(id = object_id)
model_b = Calc()
for field in model_a._meta.fields:
setattr(model_b, field.name, getattr(model_a, field.name))
model_b.pk = None
model_b.save()
return self.get_success_url(idnumber = model_b.pk) # you never return this value
def form_valid(self, form):
#objects
if self.object.checked == True:
object_id = self.object.id
return HttpResponseRedirect(self.archive_calc(object_id)) # you never return a `HttpResponse`
#save -- If this is where you are saving... you can store the value from archive and return it after saving
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('detail', kwargs = {'pk': kwargs['idnumber']})
else:
return reverse_lazy('detail', args = (self.object.id,))
Also you don't need to manually copy the fields, just do (assuming there are no unique constraints because if there were, your version would fail too):
def archive_calc(self, object_id):
c = self.model.objects.get(id = object_id)
c.pk = None
c.save()
return self.get_success_url(idnumber = c.pk)
After playing around with #Ngenator's answer and various other posts on here I have the following working code. However its not very nice to look at :(
def get_success_url(self):
if self.pknumber != None:
return reverse_lazy('pstdetail', args = (self.pknumber,))
else:
return reverse_lazy('pstdetail', args = (self.object.id,))
I have this self.pknumber = model_b.pk in the necessary place within the view and self.pknumber = None else where to enable the if statement to build the required url. Hope this helps anyone and feel free to point out any errors/improvements.
https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/UpdateView/
You cannot pass parameters in get_success_url(self) method. You can only refer to self parameter. For example self.kwargs['pk'].