views.py 19.9 KB
Newer Older
1
2
3
4
# standard library imports
from datetime import date
from cStringIO import StringIO
# core django imports
Daniel W Bond's avatar
Daniel W Bond committed
5
6
from django.views.generic import View, DetailView, ListView, CreateView,\
    UpdateView, DeleteView
7
from django.http import Http404, HttpResponseForbidden
8
from django.forms.widgets import HiddenInput
9
from django.core.urlresolvers import reverse
10
from django.core.files.uploadedfile import SimpleUploadedFile
11
12
13
from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
from django.template import Context
14
from django.contrib import messages
15
from django.utils.safestring import mark_safe
16
# third party imports
17
import requests
18
from PIL import Image
19
from braces.views import LoginRequiredMixin
20
21
from braces.views import FormValidMessageMixin
from ratelimit.mixins import RatelimitMixin
22
23
# imports from your apps
from .models import Listing, Bid, Flag, Rating
24
25
from .forms import ListingForm, BidForm, FlagForm, SellListingForm,\
    UnSellListingForm, RatingForm
26
from core.models import Student
27

Daniel W Bond's avatar
Daniel W Bond committed
28

29
30
# pulls worldcat metadata from ISBNs
def ISBNMetadata(standardISBN):
31
    # passing in numbers starting with 0 throws "SyntaxError: invalid token"
32
33
34
35
36
37
38
39
40
41
    url = "http://xisbn.worldcat.org/webservices/xid/isbn/" + str(standardISBN) + "?method=getMetadata&format=json&fl=title,year,author,ed"
    metadata = requests.get(url)
    # format into a dictionary
    dejson = metadata.json()
    try:
        metadataDict = dejson.get('list')
        return metadataDict[0]
    except:
        return None

Daniel W Bond's avatar
Daniel W Bond committed
42

43
44
45
# flagging
# you can only flag a listing once...
def can_flag(flagger, listing):
Daniel W Bond's avatar
Daniel W Bond committed
46
47
    user_flag_num = Flag.objects.filter(flagger=flagger,
                                        listing=listing).count()
48
49
50
51
52
53
    # we're assuming that this isn't going to go over 1
    if user_flag_num:
        return False
    else:
        return True

Daniel W Bond's avatar
Daniel W Bond committed
54

55
56
57
58
59
60
61
# get the listing's slug to pass to the create flag page
def flag_slug(flagger, listing):
    if not can_flag(flagger, listing):
        return Flag.objects.get(flagger=flagger, listing=listing).slug
    else:
        return None

62

Daniel W Bond's avatar
Daniel W Bond committed
63
64
65
66
67
68
69
70
71
72
73
74
# rating
# (basically) duplicated code!!!
def can_rate(rater, listing):
    user_rate_num = Rating.objects.filter(rater=rater,
                                           listing=listing).count()
    # we're assuming that this isn't going to go over 1
    if user_rate_num:
        return False
    else:
        return True


Daniel W Bond's avatar
Daniel W Bond committed
75
76
77
class ListListings(LoginRequiredMixin, ListView):
    model = Listing
    context_object_name = 'listings'
78
    paginate_by = 15
Daniel W Bond's avatar
Daniel W Bond committed
79
    queryset = Listing.objects.exclude(cancelled=True).order_by('-created')
80
    template_name = 'list_listings.html'
81
    login_url = 'login'
82

Daniel W Bond's avatar
Daniel W Bond committed
83

84
class CreateListing(LoginRequiredMixin, FormValidMessageMixin, CreateView):
85
    model = Listing
Daniel W Bond's avatar
Daniel W Bond committed
86
87
    fields = ['isbn', 'title', 'author', 'edition', 'year', 'course_abbr',
              'condition', 'access_code', 'price', 'photo', 'description']
88
89
90
    template_name = 'create_listing.html'
    context_object_name = 'listing'
    # ISBN query!
91
    login_url = 'login'
92

93
    # eventually figure out how to use {% url 'create_listing' %}
94
95
    form_valid_message = mark_safe("""Your listing was successfully created!
                         <a href="/share/new/">Create another here!</a>""")
96

97
    def form_valid(self, form):
98
99
        me = Student.objects.get(user=self.request.user)

100
        form.instance.seller = me
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

        image_name = form.instance.photo.name
        user_image = Image.open(form.instance.photo)
        image_format = user_image.format

        print user_image
        width, height = user_image.size
        print user_image.size
        print width, "width"
        print height, "height"
        maxsize = (2560, 1920)
        # five megapixels is 2560x1920
        if (width > 2560) or (height > 1920):
            user_image.thumbnail(maxsize)

            temp_image = StringIO()
            user_image.save(temp_image, image_format)
            temp_image.seek(0)

            new_uploaded_file = SimpleUploadedFile(image_name,
                            temp_image.read(), content_type=image_format)

            form.instance.photo = new_uploaded_file

125
        return super(CreateListing, self).form_valid(form)
126

127
128
    def get_context_data(self, **kwargs):
        context = super(CreateListing, self).get_context_data(**kwargs)
129

130
131
        form = ListingForm()
        context['my_form'] = form
132
133
        return context

Daniel W Bond's avatar
Daniel W Bond committed
134

135
136
# These next two views are tied together...
class DetailListing(DetailView):
137
138
    model = Listing
    context_object_name = 'listing'
139
    template_name = 'detail_listing.html'
140

Daniel W Bond's avatar
Daniel W Bond committed
141
142
    def get_context_data(self, **kwargs):
        context = super(DetailListing, self).get_context_data(**kwargs)
143
        me = Student.objects.get(user=self.request.user)
144
145

        # make the form available to the template on get
146
        # set the bidder and the listing
147
        form = BidForm(initial={'listing': self.get_object()})
148
        form.fields['listing'].widget = HiddenInput()
149
150
        context['my_form'] = form

Daniel W Bond's avatar
Daniel W Bond committed
151
152
153
154
155
        try:
            context['rating'] = Rating.objects.get(listing=self.get_object())
        except:
            context['rating'] = False

Daniel W Bond's avatar
Daniel W Bond committed
156
        # bids, filter by listing name of the current listing, order by date created
157
        context['bids'] = Bid.objects.filter(listing=self.get_object()).order_by('-price')
158
159
160
161
        context['bid_count'] = Bid.objects.filter(listing=self.get_object).count()
        # flags
        context['flags'] = Flag.objects.filter(listing=self.get_object()).order_by('-created')
        context['flag_count'] = Flag.objects.filter(listing=self.get_object()).count()
162
163
        context['can_flag'] = can_flag(me, self.get_object())
        context['flag_slug'] = flag_slug(me, self.get_object())
Daniel W Bond's avatar
Daniel W Bond committed
164
165
        return context

166

167
168
class CreateBid(CreateView):
    model = Bid
Daniel W Bond's avatar
Daniel W Bond committed
169
    fields = ['listing', 'price', 'text', ]
170
    context_object_name = 'bid'
171
    template_name = 'detail_listing.html'
172

173
174
175
176
177
178
    def form_valid(self, form):
        me = Student.objects.get(user=self.request.user)

        form.instance.bidder = me
        return super(CreateBid, self).form_valid(form)

179
    def get_success_url(self):
Daniel W Bond's avatar
Daniel W Bond committed
180
181
182
        return reverse('detail_listing',
                       kwargs={'slug': self.object.listing.slug})

183

184
185
# ...to make this single view
class ListingPage(LoginRequiredMixin, View):
186
    login_url = 'login'
187
188
189
190
191
192
193
194
195
196
197
198

    # see this page for an explanation
    # https://docs.djangoproject.com/en/1.7/topics/class-based-views/mixins/#an-alternative-better-solution

    def get(self, request, *args, **kwargs):
        view = DetailListing.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = CreateBid.as_view()
        return view(request, *args, **kwargs)

199

Daniel W Bond's avatar
Daniel W Bond committed
200
# and we return to our regularly schedule programming
201
202
class CreateFlag(LoginRequiredMixin, CreateView):
    model = Flag
Daniel W Bond's avatar
Daniel W Bond committed
203
    fields = ['reason', ]
204
    template_name = 'create_flag.html'
205
    context_object_name = 'flag'
206
    login_url = 'login'
207

208
209
210
211
212
213
214
215
216
217
218
219
    def form_valid(self, form):
        me = Student.objects.get(user=self.request.user)

        current_url = self.request.get_full_path()
        listing_slug = current_url.split('/')[3]
        # [u'', u'share', u'listing', u'C1s3oD', u'flag']
        selected_listing = Listing.objects.get(slug=listing_slug)

        form.instance.flagger = me
        form.instance.listing = selected_listing
        return super(CreateFlag, self).form_valid(form)

220
    def get_success_url(self):
Daniel W Bond's avatar
Daniel W Bond committed
221
222
        return reverse('detail_listing',
                       kwargs={'slug': self.object.listing.slug})
223
224
225
226
227

    def get_context_data(self, **kwargs):
        context = super(CreateFlag, self).get_context_data(**kwargs)
        me = Student.objects.get(user=self.request.user)

228
        # duplicated code!!!
229
230
231
232
233
234
235
236
237
        current_url = self.request.get_full_path()
        listing_slug = current_url.split('/')[3]
        # [u'', u'share', u'listing', u'C1s3oD', u'flag']
        selected_listing = Listing.objects.get(slug=listing_slug)

        selling_student = selected_listing.seller

        # you can't flag your own listing
        if (selling_student == me):
238
            return HttpResponseForbidden()
239
240
241

        # can only create a flag if you haven't previously created one
        if not can_flag(me, selected_listing):
242
243
            # because the page shouldn't exist in this scenario
            raise Http404
244
245

        context['listing'] = selected_listing
246
247
248

        form = FlagForm()
        context['my_form'] = form
249
250
        return context

Daniel W Bond's avatar
Daniel W Bond committed
251

252
253
class DeleteFlag(LoginRequiredMixin, DeleteView):
    model = Flag
254
    context_object_name = 'flag'
255
    template_name = 'delete_flag.html'
256
    login_url = 'login'
257
258

    def get_success_url(self):
Daniel W Bond's avatar
Daniel W Bond committed
259
260
        return reverse('detail_listing',
                       kwargs={'slug': self.object.listing.slug})
261
262
263
264
265

    def get_context_data(self, **kwargs):
        context = super(DeleteFlag, self).get_context_data(**kwargs)
        me = Student.objects.get(user=self.request.user)

266
        flag_student = self.get_object().flagger
267

268
269
270
        # if you didn't create the flag, you can't delete the flag
        if not(me == flag_student):
            return HttpResponseForbidden()
271
272
273

        return context

Daniel W Bond's avatar
Daniel W Bond committed
274

275
276
277
278
279
280
class DeleteBid(LoginRequiredMixin, DeleteView):
    model = Bid
    success_url = '/'

    # can be deleted by either creator or person for lister

Daniel W Bond's avatar
Daniel W Bond committed
281

282
283
284
285
286
287
class EditBid(LoginRequiredMixin, UpdateView):
    model = Bid
    success_url = '/'

    # can only be edited by the bidder

Daniel W Bond's avatar
Daniel W Bond committed
288

289
class EditListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView):
Daniel W Bond's avatar
Daniel W Bond committed
290
    model = Listing
291
292
    template_name = 'listing_edit.html'
    context_object_name = 'listing'
293
    #form_class = EditListingForm
294
    login_url = 'login'
Daniel W Bond's avatar
Daniel W Bond committed
295

296
    form_valid_message = "Your listing was successfully updated!"
297

Daniel W Bond's avatar
Daniel W Bond committed
298
299
    fields = ['title', 'author', 'isbn', 'year', 'edition', 'condition',
              'access_code', 'description', 'price', 'photo', ]
300
301
302
303
304
    template_suffix_name = '_edit'

    def get_context_data(self, **kwargs):
        context = super(EditListing, self).get_context_data(**kwargs)

305
306
        me = Student.objects.get(user=self.request.user)
        selling_student = self.get_object().seller
307

308
        if not(selling_student == me):
309
            return HttpResponseForbidden()
310

Daniel W Bond's avatar
Daniel W Bond committed
311
312
        return context

313

314
class SellListing(LoginRequiredMixin, RatelimitMixin, FormValidMessageMixin, UpdateView):
315
    model = Listing
Daniel W Bond's avatar
Daniel W Bond committed
316
    fields = ['email_message', 'winning_bid', ]
317
    template_suffix_name = '_sell'
318
319
    context_object_name = 'listing'
    template_name = 'listing_sell.html'
320
    login_url = 'login'
321

322
323
324
325
326
327
    ratelimit_key = 'user'
    ratelimit_rate = '1/d'
    ratelimit_block = False
    ratelimit_method = 'POST'

    form_valid_message = "Your email was successfully sent!"
328

329
    def form_valid(self, form):
330
        # filling out fields
331
        today = date.today()
332
        self.obj = self.get_object()
333
334
335

        form.instance.sold = True
        form.instance.date_closed = today
336
337

        # sending email
Daniel W Bond's avatar
Daniel W Bond committed
338
        # I'm still second guessing as to whether this should be in this method
339
340
341
        text_email = get_template('email/sold.txt')
        html_email = get_template('email/sold.html')

Daniel W Bond's avatar
Daniel W Bond committed
342
343
344
345
346
347
348
        email_context = Context({
            'bidder_first_name': form.instance.winning_bid.bidder.user.first_name,
            'seller_name': self.obj.seller.user.get_full_name(),
            'bid_num': form.instance.winning_bid.price,
            'listing_title': self.obj.title,
            'seller_email': self.obj.seller.user.email,
            'email_message': form.instance.email_message, })
349

Daniel W Bond's avatar
Daniel W Bond committed
350
        subject, from_email, to, cc = ('Your bid has been selected on Bookshare!',
Daniel W Bond's avatar
Daniel W Bond committed
351
                                       'no-reply@bookshare.srct.io',
352
353
354
355
                                       #form.instance.winning_bid.bidder.user.email,
                                       #self.obj.seller.user.email)
                                       'success@simulator.amazonses.com',
                                       'success@simulator.amazonses.com')
356
        text_content = text_email.render(email_context)
357
        html_content = html_email.render(email_context)
Daniel W Bond's avatar
Daniel W Bond committed
358
359
        msg = EmailMultiAlternatives(subject, text_content,
                                     from_email, [to], [cc])
360
        msg.attach_alternative(html_content, "text/html")
361
362
        msg.send()

363
364
        return super(SellListing, self).form_valid(form)

365
366
367
    def get_context_data(self, **kwargs):
        context = super(SellListing, self).get_context_data(**kwargs)

368
369
        me = Student.objects.get(user=self.request.user)
        selling_student = self.get_object().seller
370

371
        if not(selling_student == me):
372
            return HttpResponseForbidden()
373

374
375
        bid_count = Bid.objects.filter(listing=self.get_object).count()
        if bid_count < 1:
376
            # because the page shouldn't exist in this scenario
Daniel W Bond's avatar
Daniel W Bond committed
377
378
            raise Http404

379
        form = SellListingForm()
380
381
382
383
        form.fields['winning_bid'].queryset = Bid.objects.filter(listing=self.get_object())

        context['my_form'] = form

Daniel W Bond's avatar
Daniel W Bond committed
384
385
        return context

386

387
class UnSellListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView):
388
    model = Listing
389
    fields = ['email_message', ]
390
    template_suffix_name = '_unsell'
391
392
    context_object_name = 'listing'
    template_name = 'listing_unsell.html'
393
    login_url = 'login'
Daniel W Bond's avatar
Daniel W Bond committed
394

395
    form_valid_message = """Your sale has been successfully cancelled,
396
                     and your email successfully sent!"""
397

398
    def form_valid(self, form):
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
        self.obj = self.get_object()
        text_email = get_template('email/unsold.txt')
        html_email = get_template('email/unsold.html')

        email_context = Context({
            'bidder_first_name': self.obj.winning_bid.bidder.user.first_name,
            'seller_name': self.obj.seller.user.get_full_name(),
            'bid_num': self.obj.winning_bid.price,
            'listing_title': self.obj.title,
            'seller_email': self.obj.seller.user.email,
            'seller_email': form.instance.email_message, })

        subject, from_email, to, cc = ('Your transaction has been cancelled on Bookshare',
                                       'no-reply@bookshare.srct.io',
                                       #self.obj.winning_bid.bidder.user.email,
                                       #self.obj.seller.user.email)
                                       'success@simulator.amazonses.com',
                                       'success@simulator.amazonses.com')
        text_content = text_email.render(email_context)
        html_content = html_email.render(email_context)
        msg = EmailMultiAlternatives(subject, text_content,
                                     from_email, [to], [cc])
        msg.attach_alternative(html_content, "text/html")
        msg.send()

        # this has to come after the email has been sent, otherwise these are
        # cleaned out 
426
427
428
        form.instance.sold = False
        form.instance.date_closed = None
        form.instance.winning_bid = None
429

430
431
        return super(UnSellListing, self).form_valid(form)

Daniel W Bond's avatar
Daniel W Bond committed
432
    def get_context_data(self, **kwargs):
433
        context = super(UnSellListing, self).get_context_data(**kwargs)
Daniel W Bond's avatar
Daniel W Bond committed
434

435
436
        me = Student.objects.get(user=self.request.user)
        selling_student = self.get_object().seller
Daniel W Bond's avatar
Daniel W Bond committed
437

438
        if not(selling_student == me):
439
            return HttpResponseForbidden()
Daniel W Bond's avatar
Daniel W Bond committed
440

441
442
443
        form = UnSellListingForm()
        context['my_form'] = form

Daniel W Bond's avatar
Daniel W Bond committed
444
        return context
445

Daniel W Bond's avatar
Daniel W Bond committed
446

447
class CancelListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView):
Daniel W Bond's avatar
Daniel W Bond committed
448
    model = Listing
449
    fields = []
450
    template_suffix_name = '_cancel'
451
452
    context_object_name = 'listing'
    template_name = 'listing_cancel.html'
453
    login_url = 'login'
Daniel W Bond's avatar
Daniel W Bond committed
454

455
456
    form_valid_message = "Your listing was successfully cancelled!"

457
458
459
460
461
462
463
    def form_valid(self, form):
        today = date.today()

        form.instance.cancelled = True
        form.instance.date_closed = today
        return super(CancelListing, self).form_valid(form)

Daniel W Bond's avatar
Daniel W Bond committed
464
    def get_context_data(self, **kwargs):
465
        context = super(CancelListing, self).get_context_data(**kwargs)
Daniel W Bond's avatar
Daniel W Bond committed
466

467
468
        me = Student.objects.get(user=self.request.user)
        selling_student = self.get_object().seller
Daniel W Bond's avatar
Daniel W Bond committed
469

470
        if not(selling_student == me):
471
            return HttpResponseForbidden()
Daniel W Bond's avatar
Daniel W Bond committed
472

Daniel W Bond's avatar
Daniel W Bond committed
473
        return context
474

Daniel W Bond's avatar
Daniel W Bond committed
475

476
class ReopenListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView):
477
    model = Listing
478
    fields = []
479
    template_suffix_name = '_reopen'
480
481
    context_object_name = 'listing'
    template_name = 'listing_reopen.html'
482
    login_url = 'login'
483

484
    form_valid_message = "Your listing was successfully reopened!"
485

486
487
488
489
490
    def form_valid(self, form):
        form.instance.cancelled = False
        form.instance.date_closed = None
        return super(ReopenListing, self).form_valid(form)

491
492
493
    def get_context_data(self, **kwargs):
        context = super(ReopenListing, self).get_context_data(**kwargs)

494
495
        me = Student.objects.get(user=self.request.user)
        selling_student = self.get_object().seller
496

497
        if not(selling_student == me):
498
            return HttpResponseForbidden()
499

Daniel W Bond's avatar
Daniel W Bond committed
500
        return context
Daniel W Bond's avatar
Daniel W Bond committed
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600


class CreateRating(LoginRequiredMixin, CreateView):
    model = Rating
    fields = ['stars', 'review', ]
    template_name = 'create_rating.html'
    context_object_name = 'rating'
    login_url = 'login'

    def form_valid(self, form):
        me = Student.objects.get(user=self.request.user)

        current_url = self.request.get_full_path()
        listing_slug = current_url.split('/')[3]
        # [u'', u'share', u'listing', u'C1s3oD', u'flag']
        selected_listing = Listing.objects.get(slug=listing_slug)

        form.instance.rater = me
        form.instance.listing = selected_listing
        return super(CreateRating, self).form_valid(form)

    def get_success_url(self):
        return reverse('ratings',
                       kwargs={'slug': self.object.listing.seller.slug})

    def get_context_data(self, **kwargs):
        context = super(CreateRating, self).get_context_data(**kwargs)
        me = Student.objects.get(user=self.request.user)

        # duplicated code!!!
        current_url = self.request.get_full_path()
        listing_slug = current_url.split('/')[3]
        # [u'', u'share', u'listing', u'C1s3oD', u'flag']
        selected_listing = Listing.objects.get(slug=listing_slug)

        winning_student = selected_listing.winning_bid.bidder

        # you can only rate a listing that you won
        if not (winning_student == me):
            return HttpResponseForbidden()

        # can only create a rating if you haven't previously created one
        if not can_rate(me, selected_listing):
            # because the page shouldn't exist in this scenario
            raise Http404

        context['listing'] = selected_listing

        form = RatingForm()
        context['my_form'] = form
        return context


class EditRating(LoginRequiredMixin, UpdateView):
    model = Rating
    template_name = 'rating_edit.html'
    context_object_name = 'rating'
    #form_class = EditListingForm
    login_url = 'login'

    fields = ['stars', 'review', ]

    template_suffix_name = '_edit'

    def get_success_url(self):
        return reverse('ratings',
                       kwargs={'slug': self.object.listing.seller.slug})

    def get_context_data(self, **kwargs):
        context = super(EditRating, self).get_context_data(**kwargs)

        me = Student.objects.get(user=self.request.user)
        rating_student = self.get_object().rater

        if not(rating_student == me):
            return HttpResponseForbidden()

        return context


class DeleteRating(LoginRequiredMixin, DeleteView):
    model = Rating
    context_object_name = 'rating'
    template_name = 'delete_rating.html'
    login_url = 'login'

    def get_success_url(self):
        return reverse('detail_listing',
                       kwargs={'slug': self.object.listing.slug})

    def get_context_data(self, **kwargs):
        context = super(DeleteRating, self).get_context_data(**kwargs)

        me = Student.objects.get(user=self.request.user)
        rating_student = self.get_object().rater

        if not(rating_student == me):
            return HttpResponseForbidden()

        return context