Esempio n. 1
0
def product_download(request, course_slug, resource_url):
    try:
        product = Product.get_from_slug(course_slug)
        me = get_me(request)

        if ProductPurchase.objects.filter(
                product=product,
                purchase__person=me,
                purchase__refunded=False,
        ).all().count() > 0:
            view_dir = os.path.abspath(
                os.path.dirname(os.path.dirname(__file__)))
            with open(
                    os.path.join(view_dir, "products", "downloads",
                                 course_slug, resource_url), "rb") as f:
                content = f.read()

            mime_type = magic.from_buffer(content, mime=True)
            response = HttpResponse(content, content_type=mime_type)
            response['Content-Type'] = mime_type
            response['Content-Length'] = len(content)
            return response

        return redirect(reverse('login'))
    except:
        import traceback
        traceback.print_exc()
        return redirect(reverse('login'))
Esempio n. 2
0
def endpoint(request):
    openid_request = get_server().decodeRequest(dict(request.REQUEST.items()))
    if openid_request is None:
        return HttpResponse("This was not valid OpenID request")

    if openid_request.mode not in ("checkid_immediate", "checkid_setup"):
        return convertToHttpResponse(get_server().handleRequest(openid_request))

    djangoid = get_djangoid_user_from_identity()
    if not request.user.is_authenticated():
        return redirect_to_login(
            urllib.quote(openid_request.encodeToURL(request.build_absolute_uri())),
            login_url=reverse('login'))
    if request.user != djangoid.user:
        raise Exception("Logged in as %s while expecting %s" % (request.user, djangoid.user))

    # Is the user authenticated, and does he trust this trust_root?
    # user logged in (using openid_request.identity and openid_request.trust_root)
    if djangoid.authenticate(openid_request.trust_root):
        response = openid_request.answer(True)
    elif openid_request.immediate:
        response = openid_request.answer(False, request.META["HTTP_HOST"])
    else:
        if openid_request.claimed_id is None:
            openid_request.claimed_id = openid_request.identity
        redirect = openid_request.encodeToURL(reverse('openid_accept'))
        return HttpResponseRedirect(redirect)
    return convertToHttpResponse(response)
Esempio n. 3
0
def endpoint(request):
    openid_request = get_server().decodeRequest(dict(request.REQUEST.items()))
    if openid_request is None:
        return HttpResponse("This was not valid OpenID request")

    if openid_request.mode not in ("checkid_immediate", "checkid_setup"):
        return convertToHttpResponse(
            get_server().handleRequest(openid_request))

    djangoid = get_djangoid_user_from_identity()
    if not request.user.is_authenticated():
        return redirect_to_login(urllib.quote(
            openid_request.encodeToURL(request.build_absolute_uri())),
                                 login_url=reverse('login'))
    if request.user != djangoid.user:
        raise Exception("Logged in as %s while expecting %s" %
                        (request.user, djangoid.user))

    # Is the user authenticated, and does he trust this trust_root?
    # user logged in (using openid_request.identity and openid_request.trust_root)
    if djangoid.authenticate(openid_request.trust_root):
        response = openid_request.answer(True)
    elif openid_request.immediate:
        response = openid_request.answer(False, request.META["HTTP_HOST"])
    else:
        if openid_request.claimed_id is None:
            openid_request.claimed_id = openid_request.identity
        redirect = openid_request.encodeToURL(reverse('openid_accept'))
        return HttpResponseRedirect(redirect)
    return convertToHttpResponse(response)
Esempio n. 4
0
def start_journey(request, hashid):
    Organization.get()
    me = get_me(request)
    pp = ProductPurchase.objects.get(hashid=hashid)
    Journey.objects.create(productpurchase=pp, start_date=timezone.now())
    if pp.purchase.person != me:
        return redirect(reverse('logout'))

    me = get_me(request)
    return redirect(reverse("products:productpurchase", args=(pp.hashid, )))
Esempio n. 5
0
 def delete_account_link(self):
     return "%s%s" % (
         settings.MAIL_BASE_URL,
         reverse("inkmail:delete_account",
                 args=(self.delete_hash, ),
                 host='mail'),
     )
Esempio n. 6
0
    def test_invalid_opt_in_click(self):
        self.assertEquals(Subscription.objects.count(), 0)
        email = Factory.rand_email()
        name = Factory.rand_name()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse(
                'inkmail:subscribe',
            ),
            {
                'first_name': name,
                'email': email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            },
        )
        self.assertEquals(response.status_code, 200)
        process_outgoing_message_queue()

        self.assertEquals(len(mail.outbox), 1)
        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertIn(s.opt_in_link, mail.outbox[0].body)

        self.get("%s%s" % (s.opt_in_link, Factory.rand_str(length=2)))
        s = Subscription.objects.get(pk=s.pk)
        self.assertEquals(s.double_opted_in, False)
        self.assertEquals(s.double_opted_in_at, None)
Esempio n. 7
0
def checkout_help_edition(request, course_slug):
    o = Organization.get()
    product = Product.get_from_slug(course_slug)
    try:
        me = get_me(request)
    except:
        return redirect(
            "%s?help_edition=1" %
            reverse("products:course_purchase", args=(product.slug, )))

    purchase, purchase_created = Purchase.objects.get_or_create(
        person=me,
        help_edition=True,
    )
    purchase.total = Decimal(0.00)
    purchase.save()
    pp, pp_created = ProductPurchase.objects.get_or_create(
        product=product,
        purchase=purchase,
    )
    pp.send_purchase_email()
    # pp = ProductPurchase.objects.get(hashid=hashid)
    # if pp.purchase.person != me:
    #     return redirect(reverse('logout'))

    return TemplateResponse(
        request, 'products/%s/checkout_success.html' % (product.slug, ),
        locals())
Esempio n. 8
0
 def unsubscribe_link(self):
     return "%s%s" % (
         settings.MAIL_BASE_URL,
         reverse("inkmail:unsubscribe",
                 args=(self.unsubscribe_hash, ),
                 host='mail'),
     )
Esempio n. 9
0
def course_purchase(request, course_slug):
    o = Organization.get()
    product = Product.get_from_slug(course_slug)
    purchased = False
    try:
        me = get_me(request)
        if me.products.filter(product=product).count() > 0:
            purchased = True
            pp = me.products.filter(product=product)[0]
            return redirect(
                reverse('products:productpurchase', args=(pp.hashid, )))
    except:
        pass
    # if not me and request.method == 'GET':
    #     cached_resp = cache.get("%s_purchase_not_logged_in%s" % (course_slug, request.META["QUERY_STRING"]))
    #     if cached_resp:
    #         return cached_resp
    #     else:
    #         resp = TemplateResponse(
    #             request, 'products/%s/purchase.html' % (
    #                 product.slug,
    #             ),
    #             locals()
    #         )
    #         resp.render()
    #         cache.set("%s_purchase_not_logged_in%s" % (course_slug, request.META["QUERY_STRING"]), resp)
    #         return resp

    resp = TemplateResponse(request,
                            'products/%s/purchase.html' % (product.slug, ),
                            locals())
    # resp.render()
    return resp
Esempio n. 10
0
    def test_get_transfer_subscribe_200(self):
        email = Factory.rand_email()
        name = Factory.rand_name()
        response = self.get(
            reverse(
                'inkmail:transfer_subscription',
                kwargs={
                    "transfer_code": self.newsletter.hashid,
                },
            ),
            {
                'f': name,
                'e': email,
                # 'newsletter': self.newsletter.internal_name,
            },
        )
        self.assertEquals(response.status_code, 200)
        process_outgoing_message_queue()

        self.assertEquals(len(mail.outbox), 1)
        self.assertEquals(mail.outbox[0].subject,
                          self.newsletter.welcome_message.subject)
        om = OutgoingMessage.objects.all()[0]
        self.assertIn(
            om.render_email_string(
                self.newsletter.welcome_message.body_text_unrendered),
            mail.outbox[0].alternatives[0][0])
        self.assertIn(
            om.render_email_string(
                self.newsletter.welcome_message.body_text_unrendered,
                plain_text=True), mail.outbox[0].body)
        self.assertEquals(len(mail.outbox[0].to), 1)
        self.assertEquals(mail.outbox[0].to[0], email)
        self.assertEquals(mail.outbox[0].from_email,
                          self.newsletter.full_from_email)
Esempio n. 11
0
    def test_post_subscribe_200(self):
        email = Factory.rand_email()
        name = Factory.rand_name()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse('inkmail:subscribe', ),
            {
                'first_name': name,
                'email': email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            },
        )
        self.assertEquals(response.status_code, 200)
        process_outgoing_message_queue()

        self.assertEquals(len(mail.outbox), 1)
        self.assertEquals(mail.outbox[0].subject,
                          self.newsletter.confirm_message.subject)
        self.assertEquals(OutgoingMessage.objects.count(), 1)
        om = OutgoingMessage.objects.all()[0]
        self.assertIn(
            om.render_email_string(
                self.newsletter.confirm_message.body_text_unrendered),
            mail.outbox[0].alternatives[0][0])
        self.assertIn(
            om.render_email_string(
                self.newsletter.confirm_message.body_text_unrendered,
                plain_text=True), mail.outbox[0].body)
        self.assertEquals(len(mail.outbox[0].to), 1)
        self.assertEquals(mail.outbox[0].to[0], email)
        self.assertEquals(mail.outbox[0].from_email,
                          self.newsletter.full_from_email)
Esempio n. 12
0
 def test_get_transfer_subscribe_200(self):
     email = Factory.rand_email()
     name = Factory.rand_name()
     response = self.get(
         reverse(
             'inkmail:transfer_subscription',
             kwargs={
                 "transfer_code": self.newsletter.hashid,
             },
         ),
         {
             'f': name,
             'e': email,
             # 'newsletter': self.newsletter.internal_name,
         },
     )
     self.assertEquals(response.status_code, 200)
     self.assertEquals(HistoricalEvent.objects.count(), 1)
     self.assertEquals(Person.objects.count(), 1)
     self.assertEquals(Subscription.objects.count(), 1)
     he = HistoricalEvent.objects.all()[0]
     p = Person.objects.all()[0]
     s = Subscription.objects.all()[0]
     self.assertEquals(he.event_type, "transfer-subscription")
     self.assertEquals(he.event_creator_type, "person")
     self.assertEquals(he.event_creator_pk, p.pk)
     self.assertHistoricalEventDataEquality(
         he,
         person=p,
         event_type="transfer-subscription",
         newsletter=self.newsletter,
         subscription=s,
     )
Esempio n. 13
0
    def test_post_subscribe_adds_person_and_subscription(self):
        email = Factory.rand_email()
        name = Factory.rand_name()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse('inkmail:subscribe', ),
            {
                'first_name': name,
                'email': email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            },
        )

        self.assertEquals(response.status_code, 200)

        self.assertEquals(Person.objects.count(), 1)
        p = Person.objects.all()[0]
        self.assertEquals(p.first_name, name)
        self.assertEquals(p.email, email)

        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertEquals(s.person, p)
        self.assertEquals(s.newsletter.name, self.newsletter.name)
        self.assertEquals(s.subscription_url, subscription_url)
        self.assertEquals(s.subscribed_from_ip, self._source_ip)
Esempio n. 14
0
    def test_no_first_name_subscribe_adds_person_and_subscription(self):
        email = Factory.rand_email()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse('inkmail:subscribe', ),
            json.dumps({
                'email': email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            }),
            'json',
            HTTP_X_REQUESTED_WITH='XMLHttpRequest',
        )

        json_string = response.content.decode('utf-8')
        response_data = json.loads(json_string)
        self.assertEquals(response_data["success"], True)
        self.assertEquals(response.status_code, 200)

        self.assertEquals(Person.objects.count(), 1)
        p = Person.objects.all()[0]
        self.assertEquals(p.email, email)

        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertEquals(s.person, p)
        self.assertEquals(s.newsletter.name, self.newsletter.name)
        self.assertEquals(s.subscription_url, subscription_url)
        self.assertEquals(s.subscribed_from_ip, self._source_ip)
Esempio n. 15
0
    def test_no_first_name_subscribe_adds_person_and_subscription(self):
        email = Factory.rand_email()
        response = self.get(
            reverse(
                'inkmail:transfer_subscription',
                kwargs={
                    "transfer_code": self.newsletter.hashid,
                },
            ),
            {
                'e': email,
                # 'newsletter': self.newsletter.internal_name,
            },
        )

        self.assertEquals(response.status_code, 200)

        self.assertEquals(Person.objects.count(), 1)
        p = Person.objects.all()[0]
        self.assertEquals(p.email, email)

        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertEquals(s.person, p)
        self.assertEquals(s.subscription_url, "transfer-subscription")
        self.assertEquals(s.newsletter.name, self.newsletter.name)
        self.assertEquals(s.subscribed_from_ip, self._source_ip)
Esempio n. 16
0
def bestimator_experiment(request, slug):
    if "bestid" not in request.GET:
        now = timezone.now()
        bestid = "%s%s" % (now.time(),
                           Factory.rand_str(length=10, include_emoji=False))
        bestid = bestid.encode('utf-8').hex()
        return redirect("%s?bestid=%s" % (
            reverse("products:bestimator_experiment", args=(slug, )),
            bestid,
        ))
    else:
        bestid = request.GET["bestid"]

    date = datetime.datetime.today()
    experiment = BestimatorExperiment.objects.get(slug=slug)

    answer_objs = BestimatorAnswer.objects.filter(
        experiment_choice__experiment=experiment,
        session_hash=bestid,
    )
    if answer_objs.count() > 0:
        answers = {}
        for a in answer_objs:
            answers[a.experiment_choice.slug] = a

    return locals()
Esempio n. 17
0
 def test_post_subscribe_200(self):
     email = Factory.rand_email()
     name = Factory.rand_name()
     subscription_url = Factory.rand_url()
     response = self.post(
         reverse('inkmail:subscribe', ),
         {
             'first_name': name,
             'email': email,
             'newsletter': self.newsletter.internal_name,
             'subscription_url': subscription_url,
         },
     )
     self.assertEquals(response.status_code, 200)
     self.assertEquals(HistoricalEvent.objects.count(), 1)
     self.assertEquals(Person.objects.count(), 1)
     self.assertEquals(Subscription.objects.count(), 1)
     he = HistoricalEvent.objects.all()[0]
     p = Person.objects.all()[0]
     s = Subscription.objects.all()[0]
     self.assertEquals(he.event_type, "subscribed")
     self.assertEquals(he.event_creator_type, "person")
     self.assertEquals(he.event_creator_pk, p.pk)
     self.assertHistoricalEventDataEquality(
         he,
         person=p,
         event_type="subscribed",
         newsletter=self.newsletter,
         subscription=s,
     )
Esempio n. 18
0
    def test_clicked_confirm_a_second_time(self):
        self.assertEquals(Subscription.objects.count(), 0)
        email = Factory.rand_email()
        name = Factory.rand_name()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse(
                'inkmail:subscribe',
            ),
            {
                'first_name': name,
                'email': email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            },
        )
        self.assertEquals(response.status_code, 200)
        process_outgoing_message_queue()

        self.assertEquals(len(mail.outbox), 1)
        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertIn(s.opt_in_link, mail.outbox[0].body)

        self.get(s.opt_in_link)
        s = Subscription.objects.get(pk=s.pk)
        self.assertEquals(s.double_opted_in, True)
        self.assertBasicallyEqualTimes(s.double_opted_in_at, self.now())
        first_time = s.double_opted_in_at

        # Click it again
        self.get(s.opt_in_link)
        s = Subscription.objects.get(pk=s.pk)
        self.assertEquals(s.double_opted_in, True)
        self.assertBasicallyEqualTimes(s.double_opted_in_at, first_time)
Esempio n. 19
0
def delete_link(request, hashid):
    o = Organization.get()
    link = Link.objects.get(hashid=hashid)
    if request.method == "POST" and "delete" in request.POST and request.POST["delete"] == "DO_DELETE":
        link.delete()

        return redirect(reverse('clubhouse:links', host='clubhouse'))
    return locals()
Esempio n. 20
0
def confirm_delete_journey(request, hashid):
    o = Organization.get()
    me = get_me(request)
    journey = Journey.objects.get(hashid=hashid)
    if journey.productpurchase.purchase.person != me:
        return redirect(reverse('logout'))

    return locals()
Esempio n. 21
0
 def test_normal_page_renders(self):
     t = Factory.template(content='{{rendered_page_html|safe}}')
     self.page = Factory.page(
         template=t,
     )
     url = reverse('website:page_or_post', kwargs={"page_slug": self.page.slug, },)
     response = self.get(url)
     self.assertEquals(response.status_code, 200)
Esempio n. 22
0
def delete_purchase(request, hashid):
    o = Organization.get()
    purchase = Purchase.objects.get(hashid=hashid)
    if request.method == "POST" and "delete" in request.POST and request.POST["delete"] == "DO_DELETE":
        purchase.delete()

        return redirect(reverse('clubhouse:purchases', host='clubhouse'))
    return locals()
Esempio n. 23
0
def delete_journey(request, hashid):
    o = Organization.get()
    journey = Journey.objects.get(hashid=hashid)
    if request.method == "POST" and "delete" in request.POST and request.POST["delete"] == "DO_DELETE":
        journey.delete()

        return redirect(reverse('clubhouse:journeys', host='clubhouse'))
    return locals()
Esempio n. 24
0
 def feed_links(self):
     return ({
         'rel': u'alternate',
         'href': self.feed_id()
     }, {
         'rel': u'self',
         'href': link(reverse('%s_feed' % type, 'blog'))
     })
Esempio n. 25
0
def delete_bestimator_choice(request, hashid):
    o = Organization.get()
    bestimator_choice = BestimatorExperimentChoice.objects.get(hashid=hashid)
    if request.method == "POST" and "delete" in request.POST and request.POST["delete"] == "DO_DELETE":
        bestimator_choice.delete()

        return redirect(reverse('clubhouse:bestimator_choices', host='clubhouse'))
    return locals()
Esempio n. 26
0
def delete_productday(request, hashid):
    o = Organization.get()
    productday = ProductDay.objects.get(hashid=hashid)
    if request.method == "POST" and "delete" in request.POST and request.POST["delete"] == "DO_DELETE":
        productday.delete()

        return redirect(reverse('clubhouse:productdays', host='clubhouse'))
    return locals()
Esempio n. 27
0
    def test_unsubscribe_resubscribe_updates_all_fields(self):
        self.assertEquals(len(mail.outbox), 0)
        self.create_subscribed_person()
        self.assertEquals(self.subscription.unsubscribed, False)
        self.assertEquals(self.subscription.unsubscribed_at, None)
        self.send_newsletter_message()

        self.assertEquals(len(mail.outbox), 1)
        m = mail.outbox[0]
        self.assertEquals(OutgoingMessage.objects.count(), 1)
        om = OutgoingMessage.objects.all()[0]

        # Unsubscribe
        self.assertIn(om.unsubscribe_link, m.body)
        self.get(om.unsubscribe_link)

        # Fetch updated subscription
        self.subscription = Subscription.objects.get(pk=self.subscription.pk)
        self.assertEquals(self.subscription.unsubscribed, True)
        self.assertBasicallyEqualTimes(self.subscription.unsubscribed_at, self.now())

        # Re-subscribe
        name = Factory.rand_name()
        subscription_url = Factory.rand_url()
        response = self.post(
            reverse(
                'inkmail:subscribe',
            ),
            {
                'first_name': name,
                'email': self.person.email,
                'newsletter': self.newsletter.internal_name,
                'subscription_url': subscription_url,
            },
        )
        self.assertEquals(response.status_code, 200)
        self.assertEquals(Subscription.objects.count(), 1)
        self.subscription = Subscription.objects.get(pk=self.subscription.pk)
        self.assertEquals(self.subscription.unsubscribed, False)
        self.assertEquals(self.subscription.unsubscribed_at, None)

        process_outgoing_message_queue()
        self.assertEquals(len(mail.outbox), 2)
        self.assertEquals(Subscription.objects.count(), 1)
        s = Subscription.objects.all()[0]
        self.assertIn(s.opt_in_link, mail.outbox[1].body)

        # Re-double-opt-in
        self.get(s.opt_in_link)
        self.subscription = Subscription.objects.get(pk=self.subscription.pk)
        self.assertEquals(self.subscription.unsubscribed, False)
        self.assertEquals(self.subscription.unsubscribed_at, None)
        process_outgoing_message_queue()

        # Check fields
        self.person = Person.objects.get(pk=self.person.pk)
        self.assertEquals(self.person.first_name, name)
        self.assertEquals(self.subscription.subscription_url, subscription_url)
Esempio n. 28
0
def home(request):
    o = Organization.get()
    me = get_me(request)
    products = Product.objects.filter()
    if me.products.count() == 1:
        return redirect(
            reverse("products:productpurchase",
                    args=(me.products.first().hashid, )))
    return locals()
Esempio n. 29
0
def create_resource(request):
    # o = Organization.get()
    p = Resource.objects.create()
    return redirect(
        reverse('clubhouse:resource',
                kwargs={
                    "hashid": p.hashid,
                },
                host='clubhouse'))
Esempio n. 30
0
def create_message(request):
    # o = Organization.get()
    m = Message.objects.create()
    return redirect(
        reverse('clubhouse:message',
                kwargs={
                    "hashid": m.hashid,
                },
                host='clubhouse'))
Esempio n. 31
0
    def opt_in_link(self):
        if not self.opt_in_key or self.opt_in_key_created_at < timezone.now(
        ) - OPT_IN_LINK_EXPIRE_TIME:
            self.generate_opt_in_link()

        return "%s%s" % (settings.MAIL_BASE_URL,
                         reverse("inkmail:confirm_subscription",
                                 args=(self.opt_in_key, ),
                                 host='mail'))
Esempio n. 32
0
 def feed_links(self, obj):
     return ({'rel': u'alternate', 'href': self.feed_id(obj)},
             {'rel': u'self', 'href': link(reverse(
                     '%s_feed' % type,
                     'tag/%s' % get_tags_bit(obj)))})
Esempio n. 33
0
def get_server():
    url = 'http://%s%s' % (Site.objects.get_current().domain,
                           reverse('openid_endpoint'))
    return server.Server(DjangoidStore(), url)
Esempio n. 34
0
 def feed_id(self, obj):
     if obj:
         return link(obj.get_absolute_url())
     else:
         return link('%s#comments' % reverse('post_list'))
Esempio n. 35
0
 def feed_id(self):
     return link(reverse('post_list'))
Esempio n. 36
0
 def feed_links(self, obj):
     return ({'rel': u'alternate', 'href': self.feed_id(obj)},
             {'rel': u'self', 'href': link(reverse(
                     '%s_feed' % type,
                     'comments/%s' % str(getattr(obj, 'id', ''))))})
Esempio n. 37
0
 def feed_links(self):
     return ({'rel': u'alternate', 'href': self.feed_id()},
             {'rel': u'self', 'href': link(reverse('%s_feed' % type, 'blog'))})
Esempio n. 38
0
 def feed_id(self, obj):
     if not obj:
         raise Http404
     return link(reverse('post_by_tag', tag=get_tags_bit(obj)))
Esempio n. 39
0
 def get_absolute_url(self):
     return reverse('post_detail', year=self.date.year, month=self.date.strftime('%m'), day=self.date.strftime('%d'), slug=self.slug)
Esempio n. 40
0
 def testEmailChange(self):
     self.assertEquals(self.client.login(email=self.old_email, password=self.name), True)
     response = self.client.post(reverse('profile_edit'), {'email': self.new_email})
     self.assertEqual(len(mail.outbox), 1)
Esempio n. 41
0
 def get_absolute_url(self):
     return reverse('wpimport.views.post_detail', self.id)