django-paypal ipn works fine but signal is not received - django

I have this code at the end of my models.py file
from paypal.standard.ipn.signals import payment_was_successful
def confirm_payment(sender, **kwargs):
# it's important to check that the product exists
logging.debug('** CONFIRMED PAYMENT ***') #never reached this point
try:
bfeat = BuyingFeature.objects.get(slug=sender.item_number)
except BuyingFeature.DoesNotExist:
return
# And that someone didn't tamper with the price
if int(bfeat.price) != int(sender.mc_gross):
return
# Check to see if it's an existing customer
try:
customer = User.objects.get(email=sender.payer_email)
except User.DoesNotExist:
customer = User.objects.create(
email=sender.payer_email,
first_name=sender.first_name,
last_name=sender.last_name
)
# Add a new order
CustomerOrder.objects.create(customer=customer, feature=bfeat, quantity=1, paypal_email=sender.payer_email, invoice=sender.invoice, remarks='')
payment_was_successful.connect(confirm_payment)
The whole process runs ok. Payment is complete. return_url and cancel_url work fine. notify_url was tested from the paypal sandbox's test tools and works ok. However, signal is never received.
Signal code is placed at the end of the models.py and django-paypal code is placed inside my project's directory.
(code was 'stolen' from here)
I must be doing something completely wrong. Any help would be appreciated!

In django-paypal there are two signals for basic transactions:
payment_was_successful
payment_was_flagged
You must handle both signals.

I had this problem - and having chased around a few similar questions have found a resolution for my specific case. I mention it here in case anyone else is hitting this wall.
I've not researched it thoroughly, but it looks as though it's highly dependent on which version/repository you source your copy of django-paypal from. Specifically, the version I downloaded wasn't updated to accommodate the {% csrf_token %} idiom. To get this to work, I had to add the #csrf_exempt decorator to two views:
the ipn view in paypal.standard.views
the view loaded by the return url in my django paypal dictionary (... this one flags a highly accurate error if you have debug on).

Is django-paypal there in the settings.INSTALLED_APPS?
I don't see any other reason why the signal wouldn't be fired, otherwise.

Related

Concurrency protection is not working

I go bananas here. With my attempts to implement concurrency protection.
So I figured out there are 2 ways to do it.
Optimistic - basically we add a version field to the record and each save will increase it. So if my current version field is different that one on the disc it means something got modified and I give an error.
And the pessimistic approach that means we just lock a record and it will be not possible to edit.
I implemented both ways plus concurrency package for Django and nothing works I teas on SQLLite and on Postgres with Heroku.
Below is my view method it has both approaches
for pessimistic, I lock with transaction atomic using select_for_update
and for optimistic I use concurrency django package that increases my version field on each edit and do a simple comparison with on_disc value
What I do wrong? Please help?
#login_required
def lease_edit(request,pk,uri):
logger = logging.getLogger(__name__)
title = 'Edit Lease'
uri = _get_redirect_url(request, uri)
with transaction.atomic():
post = get_object_or_404(Lease.objects.select_for_update(), pk=pk)
if request.method == "POST":
form = LeaseForm(request.POST, instance=post)
if form.is_valid():
lease = form.save(commit=False)
on_disc= Lease.objects.get(pk=post.id)
if on_disc.version > post.version:
messages.add_message(request, messages.ERROR, str(lease.id) + "-Error object was modified by another user. Please reopen object.")
return redirect(uri)
lease.save()
messages.add_message(request, messages.SUCCESS, str(lease.id) + "--SUCCESS Object saved successfully")
return redirect(uri)
else:
form = LeaseForm(instance=post)
#lease = post.lease
return render(request, 'object_edit.html', {'form': form, 'title':title, 'extend': EXTEND})
To test I just open 2 browsers with the same record and try to edit both in parallel (I use same user)
The problem is with how websites work. Currently your locking works like this:
GET request loads data
you lock the row, load, send to browser, unlock
you edit data on the browser
POST data to server
you lock the row, update, unlock
There is nothing here that keeps the row locked. Multiple instances can send data to the server since the row is unlocked after each request. This is the way web systems usually work, each request is separate and locking has to be done differently.
As for the row versioning, if I understand the code correctly you don't send the current row number to the browser but you read it while updating. This again means that you get the correct number. You should send the current number to the browser and on POST compare that number to the current number in the database. This way if something changes it detects it.
I may be wrong about this part since I'm not familiar with Django but this is my understanding of the functionality of the code. I would assume form might have the current row version but it's not compared to anything, only the current number in database and one after update (which also might change the number possibly).

Rails 4 Action Mailer Previews and Factory Girl issues

I've been running into quite an annoying issue when dealing with Rails 4 action mailer previews and factory girl. Here's an example of some of my code:
class TransactionMailerPreview < ActionMailer::Preview
def purchase_receipt
account = FactoryGirl.build_stubbed(:account)
user = account.owner
transaction = FactoryGirl.build_stubbed(:transaction, account: account, user: user)
TransactionMailer.purchase_receipt(transaction)
end
end
This could really be any action mailer preview. Lets say I get something wrong (happens every time), and there's an error. I fix the error and refresh the page. Every time this happens I get a:
"ArgumentError in Rails::MailersController#preview
A copy of User has been removed from the module tree but is still active!"
Then my only way out is to restart my server.
Am I missing something here? Any clue as to what is causing this and how it could be avoided? I've restarted my server 100 times over the past week because of this.
EDIT: It may actually be happening any time I edit my code and refresh the preview?
This answers my question:
https://stackoverflow.com/a/29710188/2202674
I used approach #3: Just put a :: in front of the offending module.
Though this is not exactly an answer (but perhaps a clue), I've had this problem too.
Do your factories cause any records to actually be persisted?
I ended up using Factory.build where I could, and stubbing out everything else with private methods and OpenStructs to be sure all objects were being created fresh on every reload, and nothing was persisting to be reloaded.
I'm wondering if what FactoryGirl.build_stubbed uses to trick the system into thinking the objects are persisted are causing the system to try and reload them (after they are gone).
Here's a snippet of what is working for me:
class SiteMailerPreview < ActionMailer::Preview
def add_comment_to_page
page = FactoryGirl.build :page, id: 30, site: cool_site
user = FactoryGirl.build :user
comment = FactoryGirl.build :comment, commentable: page, user: user
SiteMailer.comment_added(comment)
end
private
# this works across reloads where `Factory.build :site` would throw the error:
# A copy of Site has been removed from the module tree but is still active!
def cool_site
site = FactoryGirl.build :site, name: 'Super cool site'
def site.users
user = OpenStruct.new(email: 'recipient#example.com')
def user.settings(sym)
OpenStruct.new(comments: true)
end
[user]
end
site
end
end
Though I am not totally satisfied with this approach, I don't get those errors anymore.
I would be interested to hear if anyone else has a better solution.

Why is Digest::SHA1 preventing proper annotation of a model?

I am using annotate in my app and all models are successfully annotated except for user.rb, which shows the following error when I annotate:
Unable to annotate user.rb: wrong number of arguments (0 for 1)
Outside of annotating, everything else works fine. User creation, updating, deletion, login, sign out, it all works properly. I have determined that the problem is with the Digest::SHA1, which I use to create session tokens, as demonstrated below in the snippet from user.rb.
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.hash(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
remember_token = User.hash(User.new_remember_token)
end
If I remove the second (def User.hash(token)) and instead do the following:
def User.new_remember_token
SecureRandom.urlsafe_base64
end
private
def create_remember_token
remember_token = Digest::SHA1.hexdigest(User.new_remember_token.to_s)
end
then annotate is happy and successfully annotates user.rb. However, this isn't really the ruby way as my session helper utilizes that User.hash(token) call several times. What am I not understanding about Digest::SHA1.hexdigest or the way that I am utilizing it?
Looks like you're working through The Rails Tutorial.
The likely reason you're seeing issues with your User.hash method is nothing to do with Digest::SHA1, but is because the method itself is inadvertently overriding Ruby's Object#hash method, which is giving you some cryptic errors. Link to Github issue about it.
So, like this commit to the Sample App repository, rename all your instances of User.hash to User.digest and hopefully that should fix your errors.

Custom Django signal receiver getting data

I'm very new to programming and especially to Django but can't work out how to use any previous answers to my advantage....
Apologies if my question is too vague but essentially, I have two different apps, let's call them app A and app B, with data on two different databases but apps contain information on the same individual item.
I want to edit this information on my 'edit details' page while keeping the apps as separate as possible (well AppB can know about functions in AppA but not vice-versa)...I guess what I really want is a signal which works like so:
A 'submit' view within AppA which is called when I submit changes to the data (using text boxes). The data for AppA is then saved..
And a signal then sent to AppB which ideally would update its data, before the HttpResponseRedirect is performed.
Unfortunately I can't really get this to work. My problem is that if I put 'request' into the arguments for save_details, I get errors like "save_details() takes exactly 3 arguments (2 given)"....does anyone know a clever way of getting something like this to work?
My submit function in AppA looks something like this...
def submit(self, request, id):
signal_received.send(sender=self, id=id)
q = get_object_or_404(AppA, pk=id)
q.blah = request.POST.get('wibble from the form')
...
return Http.....
in my AppB signals.py file, I have put.
signal_received = django.dispatch.Signal(providing_args=['id'])
def save_details(sender, uid, **kwargs):
p = AppB.objects.get(id=id)
p.wobble = request.POST.get('wobble from the form')
...
signal.received.connect(save_details)
Obviously the above doesn't mention request in its arguments which seems to be necessary but if I add that, I get problems with the number of arguments.
(I have imported all the right things at the top of each file I think...hence me leaving that off.)
Any point about the above would be appreciated....e.g. does "request" need to be the first argument? It didn't seem to like me using "self" before but I have tried to copy as much as possible the example at the bottom of the documentation (https://docs.djangoproject.com/en/dev/topics/signals/) but the extra functionality I need in the signal receiving function is flumoxing me.
Thanks in advance...

I need to rewrite url in django

I need to rewrite url www.example.com/product/1 to www.example.com/en/product/1 when user chooses english language. (he will click on a select box that will toggle the language and set a session called 'language')
I cannot choose the django 1.4 which supports this feature. We are advised to stick with django 1.3.
Hence I tried a middleware, but as it turns out, the middleware runs for each request resulting in a endless loop.
class urlrewrite():
def process_request(self, request):
if 'i' in request.session:
if request.session.get('i','') != 0:
print "session"
request.session['i'] = request.session['i'] + 1
else:
request.session['i'] = 0
else:
request.session['i'] = 0
print "request.session['i']", request.session['i']
if request.session.get('i','') == SOME_CONSTANT and request.session.get('django_language','') == 'en':
del request.session['i']
return HttpResponseRedirect("en/"+request.META['PATH_INFO'])
Ofcourse, it doesnt work. This runs for every single request.
Kindly help me out.
Thank you
Don't write this yourself, use someone else's hard work.
Try django-cms's solution first.
==== EDIT ====
You do not need to used django-cms, just have it installed and use their Multilingual URL Middleware. This interfaces with django's regular internationalisation machinery.
http://django-cms.readthedocs.org/en/2.3.3/advanced/i18n.html
This problem can be solved by using a little trick in your urls.py file, as shown by this part of the docs: https://docs.djangoproject.com/en/1.4/ref/generic-views/#django-views-generic-simple-redirect-to.
You keep the same view, it will simple have a different URL. I think thats what you want. Make sure you choose the 1.3 version of the docs, I believe there has been some changes.