Merge branch 'master' into production

pull/91/head
eric 2017-12-15 23:15:46 -05:00
commit 145c04f808
49 changed files with 1170 additions and 308 deletions

4
.gitignore vendored
View File

@ -11,4 +11,6 @@ build
deploy/last-update
logs/*
celerybeat.pid
.gitignore~
celerybeat-schedule
.gitignore~
static/scss/*.css.map

View File

@ -22,7 +22,7 @@ to install python-setuptools in step 1:
1. Ensure MySQL and Redis are installed & running on your system.
1. Create a MySQL database and user for unglueit.
1. `sudo apt-get upgrade gcc`
1. `sudo apt-get install python-setuptools git python-lxml build_essential libssl-dev libffi-dev python2.7dev libxml2-dev libxslt-dev libmysqlclient-dev`
1. `sudo apt-get install python-setuptools git python-lxml build-essential libssl-dev libffi-dev python2.7-dev libxml2-dev libxslt-dev libmysqlclient-dev`
1. `sudo easy_install virtualenv virtualenvwrapper`
1. `git clone git@github.com:Gluejar/regluit.git`
1. `cd regluit`

View File

@ -68,6 +68,7 @@ class AcqAdmin(ModelAdmin):
class PremiumAdmin(ModelAdmin):
list_display = ('campaign', 'amount', 'description')
date_hierarchy = 'created'
fields = ('type', 'amount', 'description', 'limit')
class CampaignAdminForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
@ -77,8 +78,10 @@ class CampaignAdminForm(forms.ModelForm):
)
class Meta(object):
model = models.Campaign
fields = ('managers', 'name', 'description', 'details', 'license', 'activated', 'paypal_receiver',
'status', 'type', 'email', 'do_watermark', 'use_add_ask', )
fields = (
'managers', 'name', 'description', 'details', 'license', 'paypal_receiver',
'status', 'type', 'email', 'do_watermark', 'use_add_ask', 'charitable',
)
class CampaignAdmin(ModelAdmin):
list_display = ('work', 'created', 'status')

View File

@ -908,11 +908,16 @@ class BasePandataLoader(object):
)
if metadata.publisher: #always believe yaml
edition.set_publisher(metadata.publisher)
if metadata.publication_date: #always believe yaml
edition.publication_date = metadata.publication_date
#be careful about overwriting the work description
if metadata.description and len(metadata.description) > len(work.description):
#be careful about overwriting the work description
work.description = metadata.description
# don't over-write reasonably long descriptions
if len(work.description) < 500:
work.description = metadata.description
if metadata.creator and not edition.authors.count():
edition.authors.clear()
for key in metadata.creator.keys():

View File

@ -465,7 +465,7 @@
"pk": 150,
"model": "core.premium",
"fields": {
"description": "No premium, thanks! I just want to help unglue.",
"description": "Nothing extra, thanks! I just want to support this campaign.",
"campaign": null,
"created": "2011-11-17T22:03:37",
"amount": "0",

View File

@ -98,7 +98,7 @@
"campaign": null,
"amount": 0,
"type": "00",
"description": "No premium, thanks! I just want to help unglue.",
"description": "Nothing extra, thanks! I just want to support this campaign",
"created": "2011-11-17 22:03:37"
}
},

View File

@ -1,6 +1,8 @@
import requests
from bs4 import BeautifulSoup
from django.conf import settings
from gitenberg.metadata.pandata import Pandata
from regluit.core.bookloader import add_from_bookdatas, BasePandataLoader

View File

@ -67,13 +67,14 @@ class SpringerScraper(BaseScraper):
def get_title(self):
el = self.doc.select_one('#book-title')
value = ''
if el:
value = el.text.strip()
if value:
value = value.replace('\n', ': ', 1)
self.set('title', value)
if not value:
(SpringerScraper, self).get_title()
super(SpringerScraper, self).get_title()
def get_role(self):
if self.doc.select_one('#editors'):
@ -109,7 +110,7 @@ class SpringerScraper(BaseScraper):
@classmethod
def can_scrape(cls, url):
''' return True if the class can scrape the URL '''
return url.find('10.1007') or url.find('10.1057')
return url.find('10.1007') >= 0 or url.find('10.1057') >= 0
search_url = 'https://link.springer.com/search/page/{}?facet-content-type=%22Book%22&package=openaccess'

View File

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand
from regluit.core.bookloader import add_by_sitemap
from regluit.core.loaders import add_by_sitemap
class Command(BaseCommand):
help = "load books based on a website sitemap"

View File

@ -23,11 +23,14 @@ class Command(BaseCommand):
ebf = ebook.get_archive_ebf()
if ebf:
try:
print 'making mobi for {}'.format(work.title)
if ebf.make_mobi():
print 'made mobi for {}'.format(ebf.edition.work.title)
print 'made mobi'
i = i + 1
break
else:
print 'failed to make mobi'
except:
pass
print 'failed to make mobi'
if i >= max:
break

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_auto_20171110_1253'),
]
operations = [
migrations.AddField(
model_name='campaign',
name='charitable',
field=models.BooleanField(default=False),
),
]

View File

@ -400,6 +400,7 @@ class Campaign(models.Model):
publisher = models.ForeignKey("Publisher", related_name="campaigns", null=True)
do_watermark = models.BooleanField(default=True)
use_add_ask = models.BooleanField(default=True)
charitable = models.BooleanField(default=False)
def __init__(self, *args, **kwargs):
self.problems = []

View File

@ -1089,6 +1089,7 @@ class EbookFile(models.Model):
new_ebook = Ebook.objects.create(
edition=self.edition,
format='mobi',
provider='Unglue.it',
url=new_mobi_ebf.file.url,
rights=self.ebook.rights,
version_label=self.ebook.version_label,

View File

@ -200,9 +200,11 @@ def notify_unclaimed_gifts():
unclaimed = Gift.objects.filter(used=None)
for gift in unclaimed:
"""
send notice every 7 days
send notice every 7 days, but stop at 10x
"""
unclaimed_duration = (now() - gift.acq.created ).days
if unclaimed_duration > 70:
return
if unclaimed_duration > 0 and unclaimed_duration % 7 == 0 : # first notice in 7 days
notification.send_now([gift.acq.user], "purchase_gift_waiting", {'gift':gift}, True)
notification.send_now([gift.giver], "purchase_notgot_gift", {'gift':gift}, True)

View File

@ -385,12 +385,12 @@ class CampaignPledgeForm(forms.Form):
min_value=D('1.00'),
max_value=D('2000.00'),
decimal_places=2,
label="Pledge Amount",
label="Support Amount",
)
def amount(self):
return self.cleaned_data["preapproval_amount"] if self.cleaned_data else None
anonymous = forms.BooleanField(required=False, label=_("Make this pledge anonymous, please"))
anonymous = forms.BooleanField(required=False, label=_("Make this support anonymous, please"))
ack_name = forms.CharField(
required=False,
max_length=64,
@ -399,6 +399,7 @@ class CampaignPledgeForm(forms.Form):
ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:"))
premium_id = forms.IntegerField(required=False)
donation = forms.BooleanField(required=False, label=_("Make this a donation, not a pledge."))
premium = None
@property
@ -435,6 +436,10 @@ class CampaignPledgeForm(forms.Form):
elif preapproval_amount < self.premium.amount:
logger.info("raising form validating error")
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (self.premium.amount)))
donation = self.cleaned_data.get('donation', False)
if donation and self.premium.amount > 0:
raise forms.ValidationError(_("Sorry, donations are not eligible for premiums."))
return self.cleaned_data
class TokenCCMixin(forms.Form):

View File

@ -1,8 +1,9 @@
{% extends "registration/registration_base.html" %}
{% load sass_tags %}
{% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link type="text/css" rel="stylesheet" href="{% sass_src 'scss/pledge.scss' %}" />
{% endblock %}
{% block extra_js %}

View File

@ -3,7 +3,7 @@
{% load purchased %}
{% load lib_acqs %}
{% load bookpanel %}
{% with first_ebook=work.first_ebook supporters=work.last_campaign.supporters thumbnail=work.cover_image_thumbnail author=work.authors_short title=work.title last_campaign=work.last_campaign status=work.last_campaign.status deadline=work.last_campaign.deadline workid=work.id wishlist=request.user.wishlist.works.all %}
{% with first_ebook=work.first_ebook thumbnail=work.cover_image_thumbnail author=work.authors_short title=work.title last_campaign=work.last_campaign status=work.last_campaign.status deadline=work.last_campaign.deadline workid=work.id wishlist=request.user.wishlist.works.all %}
{% purchased %}{% lib_acqs %}{% bookpanel %}
<div class="thewholebook listview tabs {% if tab_override %}{{tab_override}}{% elif first_ebook or status == 'SUCCESSFUL' %}tabs-1{% elif status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}">
<div class="listview book-list">
@ -109,7 +109,7 @@
{% endif %}
</div>
</div>
{% if request.user.id in supporters %}
{% if not supported %}
<div class="white_text bottom_button">
{% include "book_panel_addbutton.html" %}
</div>
@ -119,7 +119,7 @@
</div>
<div class="white_text bottom_button" >
{% if work.last_campaign.type == 1 %}
<a href="{% url 'pledge' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Pledge</span></span></a>
<a href="{% url 'pledge' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Support</span></span></a>
{% elif work.last_campaign.type == 2 %}
{% if in_library %}
<a href="{% url 'purchase' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Reserve It</span></span></a>
@ -217,9 +217,9 @@
<span class="kw_id" id="l{{ workid }}" data-kw="{{setkw}}">Set <i>{{setkw}}</i></span>
</div>
{% endif %}
{% elif show_pledge %}
{% elif not supported %}
<div class="listview panelfront side1 add-wishlist">
<span class="booklist_pledge"><a href="{% url 'pledge' work_id=workid %}" class="fakeinput">Pledge</a></span>
<span class="booklist_pledge"><a href="{% url 'pledge' work_id=workid %}" class="fakeinput">Support</a></span>
</div>
{% elif show_purchase %}
<div class="listview panelfront side1 add-wishlist">
@ -241,8 +241,8 @@
<span>{% if purchased.gifts.all.count %}A gift to you!{% else %}Purchased!{% endif %}</span>
{% elif borrowed %}
<span>Borrowed! ...until</span>
{% elif request.user.id in supporters %}
<span>Pledged!</span>
{% elif supported %}
<span>Supported!</span>
{% else %}
<span>Faved!</span>
{% endif %}

View File

@ -12,9 +12,9 @@
<div class="moreinfo create-account">
<span title="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}">Login to Fave</span>
</div>
{% elif request.user.id in supporters %}
{% elif supported %}
<div class="moreinfo on-wishlist">
<a href="{% url 'work' workid %}">Pledged!</a>
<a href="{% url 'work' workid %}">Supported!</a>
</div>
{% elif supporter == request.user %}
{% if wishlist %}

View File

@ -385,6 +385,20 @@ If you want to find an interesting campaign and don't have a specific book in mi
<dd>You can offer anything you are capable of delivering, so long as it is not illegal or otherwise restricted in the United States. For instance, your premiums may not include alcohol, drugs, or firearms. For full details, consult our <a href="/terms/">Terms of Use</a>.</dd>
<dt id='donation_support'>Is my campaign eligible for charitable donation support?</dt>
<dd>
The Free Ebook Foundation may provide support for some campaigns using donations. These campaigns are subject to the following guidelines:
<ol>
<li>Proceeds of a campaign must not benefit a candidate for political office and books to be unglued shall not be primarily aimed at influencing legislation.</li>
<li>Proceeds of a campaign must not benefit organizations or individuals subject to regulation by the US Treasurys Office of Foreign Assets Control.</li>
<li>Books to be unglued with Foundation support should clearly benefit the public at large - by advancing scholarship and learning, spreading scientific knowledge, achieving artistic, literary or cultural goals, educating students, promoting literacy and reading, documenting history, meeting the needs of underserved communities, raising awareness and understanding of the world around us. </li>
<li>The amount of support requested should be limited to the reasonable costs of producing the book and procuring associated rights. When a campaign is offered using a license with a “non-commercial” restriction, it is expected that the rights holders will bear part of these expenses themselves. When the campaign beneficiary is not a US-based non-profit, documentation of expenses may be requested.</li>
</ol>
</dd>
<dt>Who is responsible for making sure a rights holder delivers premiums?</dt>
<dd>The rights holder.</dd>

View File

@ -1,12 +1,26 @@
<div class="jsmodule">
<h3 class="jsmod-title"><span>Pledging FAQs</span></h3>
<h3 class="jsmod-title"><span>Campaign Support FAQs</span></h3>
<div class="jsmod-content">
<ul class="menu level1">
<li class="first parent">
<span class="faq">How do I pledge?</span>
<span class="menu level2 answer">
Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) If you pledge enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first pledge, we'll collect your card information after you click Pledge Now. Otherwise, we'll use the card you used last time -- no need to type in your info again!
Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) If you pledge enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first time supporting a campaign, we'll collect your card information after you click Pledge Now. Otherwise, we'll use the card you used last time -- no need to type in your info again!
</span>
</li>
<li class="parent">
<span class="faq">Donation or Pledge?</span>
<span class="menu level2 answer">
Donations are fully tax-deductible in the US, but we
</span>
</li>
<li class="parent">
<span class="faq">How do I donate?</span>
<span class="menu level2 answer">
Enter your donation amount and check the donation box. If you donate enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first time supporting a campaign, we'll collect your card information after you click Donate Now. Otherwise, we'll use the card you used last time -- no need to type in your info again! Remember, donations are tax-deductible in the US.
</span>
</li>
@ -20,12 +34,19 @@
<li class="parent">
<span class="faq">When will I be charged?</span>
<span class="menu level2 answer">
If this campaign reaches its target before its deadline ({{ campaign.deadline }}), you'll be charged within a day of when the target is reached. Otherwise, your pledge will expire at midnight on {{ campaign.deadline }} (Eastern US time) and you will not be charged.
Donations will be charged right away. Pledges aren't charged unless the campaign succeeds. If this campaign reaches its target before its deadline ({{ campaign.deadline }}), you'll be charged within a day of when the target is reached. Otherwise, your pledge will expire at midnight on {{ campaign.deadline }} (Eastern US time) and you will not be charged.
</span>
</li>
<li class="parent">
<span class="faq">Will I be charged if the campaign doesn't succeed?</span>
<span class="faq">Will donations be refunded if the campaign doesn't succeed?</span>
<span class="menu level2 answer">
Sorry, no. Your donation will be used to support other qualifying Unglue.it comapigns.
</span>
</li>
<li class="parent">
<span class="faq">Will pledges be charged if the campaign doesn't succeed?</span>
<span class="menu level2 answer">
Nope!
</span>

View File

@ -1,9 +1,10 @@
{% extends 'basedocumentation.html' %}
{% load sass_tags %}
{% block title %}Your Unglue.it Account{% endblock %}
{% block extra_extra_head %}
{{ block.super }}
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link type="text/css" rel="stylesheet" href="{% sass_src 'scss/pledge.scss' %}" />
{% include "stripe_stuff.html" %}
<script>

View File

@ -90,6 +90,15 @@ Please fix the following before launching your campaign:
{% endif %}
</div>
</div>
{% if campaign.charitable %}
<div class="pledged-info">
This campaign is eligible for <a href="{% url 'faq_sublocation' 'rightsholders' 'campaigns' %}#donation_support">charitable donation support</a>.
</div>
{% elif campaign.type == 1 %}
<div class="pledged-group">
If you believe your campaign meets <a href="{% url 'faq_sublocation' 'rightsholders' 'campaigns' %}#donation_support">the criteria for charitable donation support</a>, use <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">the feedback form</a> to request a review by Free Ebook Foundation staff.
</div>
{% endif %}
</div>
<div class="preview_campaign">
@ -405,13 +414,14 @@ Please fix the following before launching your campaign:
<p>A few things to keep in mind:</p>
<ul class="bullets">
<li>For tax status reasons, premiums are not currently available to supporters who use donations instead of pledges.</li>
<li>Are your premiums cumulative? That is, if you have a $10 and a $25 premium, does the $25 pledger get everything that the $10 pledger gets also? Either cumulative or not-cumulative is fine, but make sure you've communicated clearly</li>
<li>Adding new premiums during your campaign is a great way to build momentum. If you do, make sure to leave a comment in the Comments tab of your campaign page to tell supporters (it will be automatically emailed to them). Some of them may want to change (hopefully increase) their pledge to take advantage of it.</li>
<li>Also make sure to think about how your new premiums interact with old ones. If you add a new premium at $10, will people who have already pledged $25 be automatically eligible for it or not? Again, you can choose whatever you want; just be sure to communicate clearly.</li>
</ul>
<h4>Acknowledgements</h4>
<p>Your ungluers will also automatically receive the following acknowledgements:</p>
<p>Your ungluers (including thos who use donations, will also automatically receive the following acknowledgements:</p>
<ul class="terms">
<li><em>Any amount</em> &#8212; The unglued ebook</li>
<li><em>$25 and above</em> &#8212; Their name in the acknowledgements section under "supporters"</li>

View File

@ -7,7 +7,7 @@
{% endblock %}
{% block comments_graphical %}
{% ifequal transaction.host 'credit' %}
{% if transaction.host == 'credit' %}
Your Unglue.it transaction has completed and ${{transaction.max_amount|floatformat:2|intcomma}} has been deducted from your Unglue.it credit balance.
You have ${{transaction.user.credit.available|default:"0"}} of credit left.
{% else %}
@ -19,7 +19,7 @@
{% else %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
{% endif %}
{% endifequal %}
{% endif %}
{% endblock %}
{% block comments_textual %}

View File

@ -1,4 +1,19 @@
{% load humanize %}An Ungluing!
{% load humanize %}{% if transaction.donation %}{% ifequal transaction.host 'credit' %}Your Unglue.it transaction has completed and ${{transaction.max_amount|default:"0"}} has been deducted from your Unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}{% if transaction.max_amount > transaction.amount %}Your transaction for ${{transaction.max_amount|default:"0"}} has completed. Your credit card has been charged ${{transaction.amount}} and the rest has been deducted from your unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|default:"0" }}. {% endif %}{% endifequal %}
Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, click here:
https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
In case the campaign for {{ transaction.campaign.work.title }} does not succeed, we'll use your donation in support of other ungluing campaigns which qualify for charitable support.
The Free Ebook Foundation is a US 501(c)3 non-profit organization. Our tax ID number is 61-1767266. Your gift is tax deductible to the full extent provided by the law.
For more information about the Free Ebook Foundation, visit https://ebookfoundation.org/
Thank you again for your generous support.
{{ transaction.campaign.rightsholder }} and the Unglue.it team
{% else %}An Ungluing!
Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
@ -13,4 +28,4 @@ https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
Thank you again for your support.
{{ transaction.campaign.rightsholder }} and the Unglue.it team
{% endif %}

View File

@ -7,10 +7,37 @@
{% endblock %}
{% block comments_graphical %}
Hooray! The campaign for <a href="{% url 'work' transaction.campaign.work_id %}">{{ transaction.campaign.work.title }}</a> has succeeded. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thank you again for your help.
{% if transaction.donation %}
{% if transaction.host == 'credit' %}
Your Unglue.it transaction has completed and ${{transaction.max_amount|floatformat:2|intcomma}} has been deducted from your Unglue.it credit balance.
You have ${{transaction.user.credit.available|default:"0"}} of credit left.
{% elif transaction.max_amount > transaction.amount %}
Your transaction for ${{transaction.max_amount|floatformat:2|intcomma}} has completed.
Your credit card has been charged ${{transaction.amount}} and the
rest has been deducted from your unglue.it credit balance.
You have ${{transaction.user.credit.available|intcomma}} of credit left.
{% else %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
{% endif %}
{% else %} Hooray! The campaign for <a href="{% url 'work' transaction.campaign.work_id %}">{{ transaction.campaign.work.title }}</a> has succeeded. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thank you again for your help.
{% endif %}
{% endblock %}
{% block comments_textual %}
{% if transaction.donation %}
<p>Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, <a href="{% url 'work' transaction.campaign.work_id %}">click here</a>. </p>
<p>In case the campaign for {{ transaction.campaign.work.title }} does not succeed, we'll use your donation in support of other ungluing campaigns which qualify for charitable support.</p>
<p>The Free Ebook Foundation is a US 501(c)3 non-profit organization. Our tax ID number is 61-1767266. Your gift is tax deductible to the full extent provided by the law.</p>
<p>For more information about the Free Ebook Foundation, visit <a href="https://ebookfoundation.org/">https://ebookfoundation.org/</a></p>
<p>Thank you again for your generous support.</p>
<p>{{ transaction.campaign.rightsholder }} and the Unglue.it team</p>
{% else %}
<p>Congratulations!</p>
<p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. {{ transaction.host|capfirst }} has been charged to your credit card.</p>
@ -28,4 +55,5 @@
</p>
<p>{{ transaction.campaign.rightsholder }} and the Unglue.it team
</p>
{% endif %}
{% endblock %}

View File

@ -1 +1 @@
Your pledge to the campaign to unglue {{transaction.campaign.work.title}} has been charged.
Your {% if transaction.donation %}donation{% else %}pledge{% endif %} for the campaign to unglue {{transaction.campaign.work.title}} has been charged.

View File

@ -1,4 +1,4 @@
{% if pledged %}You pledged toward it{% else %}You put it on your list{% endif %}, and now the campaign for {{ campaign.work.title}} (https://{{current_site.domain}}{% url 'work' campaign.work_id %}) has succeeded.
{% if pledged %}You supported it{% else %}You put it on your list{% endif %}, and now the campaign for {{ campaign.work.title}} (https://{{current_site.domain}}{% url 'work' campaign.work_id %}) has succeeded.
{% ifequal campaign.type 1 %}
You will notified when an Unglued ebook edition is available, within 90 days.
{% if pledged %}

View File

@ -2,9 +2,7 @@ Alas. The campaign to unglue {{ campaign.work.title }} (https://{{current_site.
If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums.
Still want to give {{ campaign.work.title }} to the world? Don't despair. Keep it on your wishlist and tell everyone why you love this book. The rights holder, {{ campaign.rightsholder }}, may run a campaign with different terms in the future. With your help, we may yet be able to unglue {{ campaign.work.title }}.
There are also other books with active campaigns that need your help: https://unglue.it/campaigns/ending .
If you donated in support of this work, your donation will be used to support other campaigns that qualify for charitable support.
Thank you for your support.

View File

@ -10,11 +10,7 @@
{% endblock %}
{% block comments_textual %}
If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums.
Still want to give {{ campaign.work.title }} to the world? Don't despair. Keep it on your faves and tell everyone why you love this book. The rights holder, {{ campaign.rightsholder }}, may run a campaign with different terms in the future. With your help, we may yet be able to unglue {{ campaign.work.title }}.
There are also <a href="https://unglue.it/campaigns/ending">other books with active campaigns</a> that need your help.
If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums. If you donated in support of this work, your donation will be used to support other campaigns that qualify for charitable support.
Thank you for your support.
{% endblock %}

View File

@ -67,26 +67,32 @@
Amount: ${{transaction.amount|floatformat:2|intcomma}}.<br />
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
</div>
<br /> You can modify your pledge below.
<br /> You can modify your pledge below. <span id="change_pledge_notice">If you change your pledge to a donation, the pledge will be cancelled and your credit card charged immediately. </span>
</div>
{% endif %}
{% ifnotequal work.last_campaign.status 'ACTIVE' %}
{% if work.last_campaign.status != 'ACTIVE' %}
<div class="clearfix"><h4>Campaign NOT ACTIVE</h4>
This pledge form is not functional because the campaign is NOT ACTIVE.<br /><br /><br />
</div>
{% endifnotequal %}
{% endif %}
{% comment %}
Even there is a CampaignPledgeForm in frontend/forms.py , the "widget" for premium_id is implemented in HTML here for now.
Even though there is a CampaignPledgeForm in frontend/forms.py , the "widget" for premium selection is implemented in HTML here for now.
{% endcomment %}
<form class="pledgeform" method="POST" action="{% if faqmenu == 'modify' %}{% url 'pledge_modify' work_id=work.id %}{% else %}{% url 'pledge' work_id=work.id %}{% endif %}">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="pledge_amount">{{ form.preapproval_amount.label_tag }}: {{ form.preapproval_amount.errors }}${{ form.preapproval_amount }}</div>
<div class="pledge_amount">{{ form.preapproval_amount.label_tag }} {{ form.preapproval_amount.errors }}${{ form.preapproval_amount }}
{% if work.last_campaign.charitable %}
<div class="pledgeform_label">{{ form.donation }} {{ form.donation.label_tag }}</div>
{% else %}
{% endif %}
</div>
<div id="select_premiums">
{% if premiums|length > 1 %}
<div class="pledge_amount premium_level">Choose your premium:</div>
<div class="pledge_amount premium_level">Choose your premium<span id="premium_note"></span>:</div>
<div style="height:10px;"></div>
@ -119,16 +125,17 @@
If the RH hasn't added any premiums, there's no point in displaying the "no premium" option, but we do need to check it off so the form will validate.
{% endcomment %}
{% endif %}
</div>
<div class="pledge_amount clearfix" id="mandatory_premiums">
<div>Depending on your pledge amount, you'll also get these acknowledgements.</div>
<div>Depending on your support amount, you'll also get these acknowledgements.</div>
<div class="ack_active"><div class="ack_level">Any amount</div><div class="ack_header">The unglued ebook will be delivered to your inbox.</div></div>
<div id="ack_name" class="ack_inactive"><div class="ack_level">$25+</div><div class="ack_header">You'll be listed on the acknowledgements page of the unglued ebook<span id="ack_section"></span>.&nbsp;{{ form.ack_name.label_tag }} {{ form.ack_name.errors }}{{ form.ack_name }}</div></div>
<div id="ack_link" class="ack_inactive"><div class="ack_level">$50+</div><div class="ack_header">Your acknowledgement will link to your Unglue.it supporter page.{{ form.ack_link }}</div></div>
<div id="ack_dedication" class="ack_inactive"><div class="ack_level">$100+</div><div class="ack_header">Your acknowledgement can include a dedication (140 characters max).&nbsp;{{ form.ack_dedication.label_tag }} {{ form.ack_dedication.errors }}{{ form.ack_dedication }}</div></div>
</div>
<div id="anonbox"><I>{{ form.anonymous.label_tag }}</I> {{ form.anonymous.errors }}{{ form.anonymous }}</div>
<input name="pledge" type="submit" {% if faqmenu == 'modify' %}value="Modify Pledge"{% else %}value="Pledge Now"{% endif %} id="pledgesubmit" class="loader-gif" />
<input name="pledge" type="submit" {% if faqmenu == 'modify' %}value="Modify Pledge"{% else %}value="Support Now"{% endif %} id="pledgesubmit" class="loader-gif" />
<input name="decoy" type="submit" id="fakepledgesubmit" disabled="disabled" />
{% comment %}
When the pledge amount and premium are in an inconsistent state, the real button is disabled and (via css) hidden; instead we display this fake button with a helpful message. It's a button so we can reuse all the existing CSS for buttons, so that it looks like the real button has just changed in appearance. It's hidden and the other one un-disabled and un-hidden when the pledge & premium return to a correct state. People without javascript enabled will miss out on the front-end corrections but form validation will catch it.

View File

@ -1,12 +1,14 @@
{% extends 'basepledge.html' %}
{% load humanize %}
{% load sass_tags %}
{% block title %}Pledge Completed{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/searchandbrowse2.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link type="text/css" rel="stylesheet" href="{% sass_src 'scss/pledge.scss' %}" />
<script src="/static/js/slides.min.jquery.js"></script>
<script src="/static/js/slideshow.js"></script>
@ -30,11 +32,13 @@
<h2 class="thank-you">Thank you!</h2>
{% if not campaign %}
<p class="pledge_complete">You've just donated ${{ transaction.amount|floatformat:2|intcomma }} to the <a href="https://ebookfoundation.org">Free Ebook Foundation</a></p>
{% endif %}
{% ifequal campaign.type 1 %}
{% elif campaign.type == 1 %}
{% if campaign.donation %}
<p class="pledge_complete">You've just donated ${{ transaction.amount|floatformat:2|intcomma }} in support of <I><a href="{% url 'work' work.id %}">{{ work.title }}</a></I>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy. Otherwise, your donation will be used to support qualifying ungluing campaigns. Your donation to the Free Ebook Foundation is tax-deductible in the US.</p>
{% else %}
<p class="pledge_complete">You've just {% if modified %}modified your pledge for{% else %}pledged{% endif %} ${{ transaction.amount|floatformat:2|intcomma }} to <I><a href="{% url 'work' work.id %}">{{ work.title }}</a></I>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
{% endifequal %}
{% ifequal campaign.type 2 %}
{% endif %}
{% elif campaign.type == 2 %}
{% if transaction.extra.give_to %}
<p class="pledge_complete">You've just paid ${{ transaction.amount|floatformat:2|intcomma }} to give a copy of <I><a href="{% url 'work' work.id %}">{{ work.title }}</a></I> to {{ transaction.extra.give_to }}. Its ungluing date is now <i>{{ campaign.cc_date }}</i>. Thanks for helping to make that day come sooner!</p>
@ -51,13 +55,12 @@
{% endif %}
<div style="height:75px;"></div>
{% endifequal %}
{% ifequal campaign.type 3 %}
{% elif campaign.type == 3 %}
<p class="pledge_complete">You've just contributed ${{ transaction.amount|floatformat:2|intcomma }} to the creators of <I><a href="{% url 'work' work.id %}">{{ work.title }}</a></I> to thank them for making it free to the world.</p>
<div><a href="{% url 'download' work.id %}" class="fakeinput" style="float:left">Download Now</a> </div>
<div style="height:75px;"></div>
{% endifequal %}
{% endif %}
<div class="modify_notification clearfix">
{% include "trans_summary.html" %}
</div>

View File

@ -1,10 +1,10 @@
{% load humanize %}
{% load libraryauthtags %}
<div class="trans_summary">
{% ifequal transaction.campaign.type 1 %}
Your pledge: ${{transaction.amount|floatformat:2|intcomma}}.<br />
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
{% if transaction.anonymous %}You asked to pledge anonymously, so you will be counted but not named on the list of supporters.<br />{% endif %}<br />
{% if transaction.campaign.type == 1 %}
Your {% if transaction.donation %}donation{% else %}pledge{% endif %}: ${{transaction.amount|floatformat:2|intcomma}}.<br />
{% if transaction.premium %}Your premium: {{ transaction.premium.description }}{% endif %}<br />
{% if transaction.anonymous %}You asked to support anonymously, so you will be counted but not named on the list of supporters.<br />{% endif %}<br />
Acknowledgements: <ul>
<li>The unglued ebook will be delivered to your inbox.</li>
{% if not transaction.anonymous %}
@ -22,9 +22,8 @@
<li>The following dedication will be included: <i>{{ transaction.extra.ack_dedication }}</i></li>
{% endif %}
</ul>
{% endifequal %}
{% if transaction.campaign.type == 2 or not transaction.campaign %}
{% ifequal transaction.host 'credit' %}
{% elif transaction.campaign.type == 2 or not transaction.campaign %}
{% if transaction.host == 'credit' %}
Amount: ${{transaction.max_amount|floatformat:2|intcomma}}.<br />
This amount has been deducted from your Unglue.it credit balance.<br />
You have ${{request.user.credit.available|default:"0"}} of credit left.<br />
@ -37,13 +36,13 @@
{% else %}
This amount has been charged to your credit card.<br />
{% endif %}
{% endifequal %}
{% endif %}
{% if transaction.campaign %}
License type: {{ transaction.offer.get_license_display }}<br />
{% ifequal transaction.offer.license 2 %}
{% if transaction.offer.license == 2 %}
Receiving library: {{ transaction.extra.library_id|libname }}<br />
Number of copies: {{ transaction.extra.copies }}
{% endifequal %}
{% endif %}
{% endif %}
{% endif %}

View File

@ -4,8 +4,10 @@
{% if work.last_campaign.type == 1 %}
{% if pledged %}
<div class="btn_support modify"><form action="{% url 'pledge_modify' work_id %}" method="get"><input type="submit" value="Modify Pledge" /></form></div>
{% elif supported %}
<div class="btn_support"><form action="{% url 'pledge' work_id %}" method="get"><input type="submit" value="Add Support" /></form></div>
{% else %}
<div class="btn_support"><form action="{% url 'pledge' work_id %}" method="get"><input type="submit" value="Pledge" /></form></div>
<div class="btn_support"><form action="{% url 'pledge' work_id %}" method="get"><input type="submit" value="Support" /></form></div>
{% endif %}
{% elif work.last_campaign.type == 3 %}
<div class="btn_support">

View File

@ -15,12 +15,12 @@ def bookpanel(context):
# campaign is ACTIVE, type 1 - REWARDS
# user has not pledged or user is anonymous
show_pledge = False
if campaign and campaign.type==REWARDS:
supported = False
if campaign and campaign.type == REWARDS:
if campaign.status == 'ACTIVE':
if user.is_anonymous() or not user.id in context.get('supporters', []):
show_pledge = True
context['show_pledge'] = show_pledge
if not user.is_anonymous() and user.transaction_set.filter(campaign__work=work):
supported = True
context['supported'] = supported
# compute a boolean that's true if bookpanel should show a "purchase" button...
# campaign is ACTIVE, type 2 - BUY2UNGLUE
@ -30,7 +30,7 @@ def bookpanel(context):
# not on the library page
show_purchase = False
if campaign and campaign.type==BUY2UNGLUE:
if campaign and campaign.type == BUY2UNGLUE:
if user.is_anonymous() or not context.get('license_is_active', False):
if campaign.status == 'ACTIVE':
if not context.get('borrowable', False):

View File

@ -347,8 +347,10 @@ def work(request, work_id, action='display'):
campaign = work.last_campaign()
editions = work.editions.all().order_by('-publication_date')[:10]
try:
pledged = campaign.transactions().filter(user=request.user, status="ACTIVE")
supported = campaign.transactions().filter(user=request.user)
pledged = supported.filter(status="ACTIVE")
except:
supported = None
pledged = None
cover_width_number = 0
@ -400,6 +402,7 @@ def work(request, work_id, action='display'):
'base_url': base_url,
'editions': editions,
'pledged': pledged,
'supported': supported,
'activetab': activetab,
'alert': alert,
'claimstatus': claimstatus,
@ -941,10 +944,15 @@ class PledgeView(FormView):
# Campaign must be ACTIVE
assert self.campaign.status == 'ACTIVE'
except Exception, e:
# this used to raise an exception, but that seemed pointless. This now has the effect of preventing any pledges.
# this used to raise an exception, but that seemed pointless.
# This now has the effect of preventing any pledges.
return {}
transactions = self.campaign.transactions().filter(user=self.request.user, status=TRANSACTION_STATUS_ACTIVE, type=PAYMENT_TYPE_AUTHORIZATION)
transactions = self.campaign.transactions().filter(
user=self.request.user,
status=TRANSACTION_STATUS_ACTIVE,
type=PAYMENT_TYPE_AUTHORIZATION
)
premium_id = self.request.GET.get('premium_id', self.request.POST.get('premium_id', 150))
if transactions.count() == 0:
ack_name = self.request.user.profile.ack_name
@ -995,39 +1003,55 @@ class PledgeView(FormView):
return context
def form_valid(self, form):
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
# right now, if there is a non-zero pledge amount, go with that.
# otherwise, do the pre_approval
donation = form.cleaned_data['donation']
p = PaymentManager()
if self.transaction:
# modifying the transaction...
assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and self.transaction.status == TRANSACTION_STATUS_ACTIVE
status, url = p.modify_transaction(self.transaction, form.cleaned_data["preapproval_amount"],
paymentReason="Unglue.it %s for %s"% (self.action, self.campaign.name) ,
pledge_extra = form.trans_extra
)
logger.info("status: {0}, url:{1}".format(status, url))
assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and \
self.transaction.status == TRANSACTION_STATUS_ACTIVE
if status and url is not None:
logger.info("PledgeView (Modify): " + url)
return HttpResponseRedirect(url)
elif status and url is None:
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_modified'), self.transaction.id))
if donation:
# cancel transaction, then proceed to make a donation
p.cancel_transaction(self.transaction)
else:
return HttpResponse("No modification made")
else:
t, url = p.process_transaction('USD', form.amount(),
host = PAYMENT_HOST_NONE,
campaign=self.campaign,
user=self.request.user,
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
pledge_extra=form.trans_extra
# modify the pledge...
status, url = p.modify_transaction(
self.transaction,
form.cleaned_data["preapproval_amount"],
paymentReason="Unglue.it %s for %s"% (self.action, self.campaign.name),
pledge_extra=form.trans_extra,
)
logger.info("status: {0}, url:{1}".format(status, url))
if status and url is not None:
logger.info("PledgeView (Modify): " + url)
return HttpResponseRedirect(url)
elif status and url is None:
return HttpResponseRedirect(
"{0}?tid={1}".format(reverse('pledge_modified'), self.transaction.id)
)
if url:
logger.info("PledgeView url: " + url)
return HttpResponseRedirect(url)
else:
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
else:
return HttpResponse("No modification made")
t, url = p.process_transaction(
'USD',
form.amount(),
host = PAYMENT_HOST_NONE,
campaign=self.campaign,
user=self.request.user,
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
pledge_extra=form.trans_extra,
donation = donation
)
if url:
logger.info("PledgeView url: " + url)
return HttpResponseRedirect(url)
else:
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
return HttpResponse(
"Our attempt to enable your transaction failed. We have logged this error."
)
class PurchaseView(PledgeView):
template_name = "purchase.html"
@ -1158,7 +1182,7 @@ class FundView(FormView):
if not self.transaction.campaign:
self.action = 'donation'
elif self.transaction.campaign.type == REWARDS:
self.action = 'pledge'
self.action = 'donation' if self.transaction.donation else 'pledge'
elif self.transaction.campaign.type == THANKS:
self.action = 'contribution'
else:

View File

@ -672,13 +672,14 @@ class PaymentManager( object ):
def process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None, user=None,
return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None,
modification=False):
def process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None,
user=None, return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None,
donation=False, modification=False):
'''
process
saves and processes a proposed transaction; decides if the transaction should be processed immediately.
saves and processes a proposed transaction; decides if the transaction should be processed
immediately.
currency: a 3-letter currency code, i.e. USD
amount: the amount to authorize
@ -690,8 +691,9 @@ class PaymentManager( object ):
modification: whether this authorize call is part of a modification of an existing pledge
pledge_extra: extra pledge stuff
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None
return value: a tuple of the new transaction object and a re-direct url.
If the process fails, the redirect url will be None
donation: transaction is a donation
'''
# set the expiry date based on the campaign deadline
if campaign and campaign.deadline:
@ -699,14 +701,16 @@ class PaymentManager( object ):
else:
expiry = now() + timedelta(days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN)
t = Transaction.create(amount=0,
host = host,
max_amount=amount,
currency=currency,
campaign=campaign,
user=user,
pledge_extra=pledge_extra
)
t = Transaction.create(
amount=0,
host = host,
max_amount=amount,
currency=currency,
campaign=campaign,
user=user,
pledge_extra=pledge_extra,
donation=donation,
)
t.save()
# does user have enough credit to transact now?
if user.is_authenticated() and user.credit.available >= amount :
@ -787,7 +791,7 @@ class PaymentManager( object ):
modify
Modifies a transaction.
2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
2 main situations: if the new amount is less than max_amount, no need to go out to Stripe again
if new amount is greater than max_amount...need to go out and get new approval.
to start with, we can use the standard pledge_complete, pledge_cancel machinery
might have to modify the pledge_complete, pledge_cancel because the messages are going to be

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payment', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='transaction',
name='donation',
field=models.BooleanField(default=False),
),
]

View File

@ -110,6 +110,9 @@ class Transaction(models.Model):
# whether the user wants to be not listed publicly
anonymous = models.BooleanField(default=False)
# whether the transaction represents a donation
donation = models.BooleanField(default=False)
@property
def tier(self):
if self.amount < 25:
@ -210,24 +213,24 @@ class Transaction(models.Model):
return pe
@classmethod
def create(cls,amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD',
status=TRANSACTION_STATUS_NONE,campaign=None, user=None, pledge_extra=None):
def create(cls, amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD',
status=TRANSACTION_STATUS_NONE, campaign=None, user=None, pledge_extra=None,
donation=False):
if user and user.is_anonymous():
user = None
t = cls.objects.create(
amount=amount,
host=host,
max_amount=max_amount,
currency=currency,
status=status,
campaign=campaign,
user=user,
donation=donation,
)
if pledge_extra:
t = cls.objects.create(amount=amount,
host=host,
max_amount=max_amount,
currency=currency,
status=status,
campaign=campaign,
user=user,
)
t.set_pledge_extra(pledge_extra)
return t
else:
return cls.objects.create(amount=amount, host=host, max_amount=max_amount, currency=currency,status=status,
campaign=campaign, user=user)
return t
class PaymentResponse(models.Model):
# The API used

View File

@ -1,14 +0,0 @@
{% load amazon_fps_tags %}
{% block content %}
<div>
<p>You are going to be charged $100 in the
<a href="https://payments.amazon.com/sdui/sdui/helpTab/Amazon-Flexible-Payments-Service/Technical-Resources/Amazon-FPS-Sandbox">Amazon FPS Sandbox</a>.</p>
<p>{% amazon_fps fps_obj %}</p>
</div>
<div>
<p>(Recurring payments) You are going to be charged $100 every hour in the
<a href="https://payments.amazon.com/sdui/sdui/helpTab/Amazon-Flexible-Payments-Service/Technical-Resources/Amazon-FPS-Sandbox">Amazon FPS Sandbox</a>.</p>
<p>{% amazon_fps fps_recur_obj %}</p>
</div>
{% endblock %}

View File

@ -24,7 +24,7 @@ certifi==2016.2.28
django-celery==3.1.17
django-ckeditor==4.5.1
#django-email-change==0.2.3
git+git://github.com/eshellman/django-email-change.git@e5076a9a2c9a9b61b58cea7e461c8368af07b2ad
git+git://github.com/eshellman/django-email-change.git@1e71dd320504d56b1fc7d447ce4cffb550cedce7
django-compat==1.0.10
django-contrib-comments==1.7.1
django-endless-pagination==2.0
@ -43,7 +43,7 @@ django-storages==1.4.1
django-tastypie==0.13.3
django-transmeta==0.7.3
feedparser==5.1.2
fef-questionnaire==4.0.0
fef-questionnaire==4.0.1
freebase==1.0.8
#gitenberg.metadata==0.1.6
git+https://github.com/gitenberg-dev/gitberg-build
@ -104,3 +104,7 @@ setuptools==25.0.0
urllib3==1.16
beautifulsoup4==4.6.0
RISparser==0.4.2
# include these 2 for development
#libsass==0.13.4
#django-compressor==2.2
django-sass-processor==0.5.6

View File

@ -74,6 +74,8 @@ STATIC_ROOT = ''
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
SASS_PROCESSOR_ROOT = os.path.join(PROJECT_DIR, 'static')
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
@ -182,8 +184,14 @@ INSTALLED_APPS = (
'transmeta',
'questionnaire',
'questionnaire.page',
'sass_processor',
)
SASS_PROCESSOR_INCLUDE_DIRS = [
os.path.join(PROJECT_DIR, 'static', 'scss'),
]
SASS_PROCESSOR_AUTO_INCLUDE = False
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.

View File

@ -85,3 +85,7 @@ UNGLUEIT_TEST_PASSWORD = None
# local settings for maintenance mode
MAINTENANCE_MODE = False
# assume that CSS will get generated on dev
SASS_OUTPUT_STYLE = 'compressed'

View File

@ -10,7 +10,9 @@ $j().ready(function() {
var submitbutton = $j('#pledgesubmit');
var fakesubmitbutton = $j('#fakepledgesubmit');
var anonbox = $j('#anonbox input');
var donationbox = $j('#id_donation');
var ackSection = $j('#ack_section');
var premium_section = $j('#select_premiums');
var supporterName = $j('#pass_supporter_name').html();
var ackName = $j('#id_ack_name').val();
var ackDedication = $j('#id_ack_dedication').val();
@ -72,6 +74,21 @@ $j().ready(function() {
$j('#'+mySpan+' input[type=text]').val('').attr('disabled', 'disabled');
}
// make premium selection inactive: greyed-out and not modifiable
var deactivate_premiums = function() {
premium_section.addClass('premiums_inactive');
$j('#premiums_list li label input').attr('disabled', 'disabled');
$j('#premiums_list li:first-child label input').removeAttr('disabled').attr('checked', 'checked');
$j('#premium_note').text(' (not available with donation)');
}
// make premium selection inactive: greyed-out and not modifiable
var activate_premiums = function() {
premium_section.removeClass('premiums_inactive');
$j('#premiums_list li label input').removeAttr('disabled');
$j('#premium_note').text('');
}
// fill mandatory premium link input with supporter page
var activateLink = function() {
$j('#ack_link').removeClass('ack_inactive').addClass('ack_active');
@ -86,6 +103,17 @@ $j().ready(function() {
deactivate('ack_name');
$j('#id_ack_name').val('Anonymous');
}
// when supporter clicks the donation box, activate/deactivate premium selection
donationbox.change(function() {
if(this.checked) {
deactivate_premiums();
$j('#change_pledge_notice').addClass('yikes');
} else {
activate_premiums();
$j('#change_pledge_notice').removeClass('yikes')
}
});
// selectively highlight/grey out acknowledgements supporter is eligible for
var rectifyAcknowledgements = function(current) {

3
static/scss/pledge.css Normal file

File diff suppressed because one or more lines are too long

345
static/scss/pledge.scss Normal file
View File

@ -0,0 +1,345 @@
@import "variables.scss";
#content-block .jsmod-content, .book-detail {
float: left;
width: auto;
}
input[type="submit"], a.fakeinput {
float: right;
font-size: $font-size-header;
margin: 10px 0 10px;
cursor: pointer;
}
.pledge_amount {
padding: 10px;
font-size: $font-size-header;
background: $pale-blue;
&.premium_level {
margin-top: 3px;
}
}
form.pledgeform {
width: 470px;
.pledgeform_label {
font-size: 80%
}
}
#id_preapproval_amount {
width: 50%;
line-height: 30px;
font-size: $font-size-larger;
}
ul.support li, ul.support li:hover {
background-image: none;
}
p {
margin: 7px auto;
}
.jsmodule.pledge {
margin: auto;
.jsmod-content {
float: right !important;
}
}
.modify_notification {
width: 452px;
margin-bottom: 7px;
border: solid 2px $blue-grey;
@include one-border-radius(5px);
padding: 7px;
h4 {
margin: 0 0 5px 0;
}
}
.cancel_notice {
width: 470px;
padding: 5px;
margin-top: 10px;
}
#fakepledgesubmit {
background-color: $alert;
cursor: default;
font-weight: bold;
font-size: $font-size-header;
display: none;
}
span.menu-item-price {
float: none !important;
}
ul#offers_list li {
div.on {
display: block;
background: $pale-blue;
margin-top: 1em;
.give_label {
padding: 4px;
color: black;
}
}
div.off {
display: none;
}
input[type=text], textarea {
width: 95%;
font-size: $font-size-larger;
color: $text-blue;
margin: 0 10px 5px 5px;
}
input[type=text]{
@include height($font-size-larger*1.3);
}
}
.premiums_inactive#select_premiums {
background: $blue-grey;
div, div.pledge_amount, ul li, ul li:hover {
background: $blue-grey;
color: $text-blue
}
}
#premium_note {
font-size: 70%;
font-style: italic;
}
#mandatory_premiums {
font-size: $font-size-larger;
div {
float: left;
&.ack_level {
width: 16%;
margin-right: 3%;
height: 100%;
padding: 1%;
}
&.ack_header {
width: 73%;
padding: 1%;
}
&.ack_active, &.ack_inactive {
width: 100%;
font-size: $font-size-default;
}
&.ack_active {
.ack_header, .ack_level {
border: solid $text-blue;
border-width: 1%;
background: white;
}
}
&.ack_inactive {
.ack_header, .ack_level {
border: solid $blue-grey;
border-width: 1%;
background: $blue-grey;
}
input, textarea {
background: $blue-grey;
border: dashed 1px $text-blue;
}
}
}
> div {
margin: 7px 0;
}
input[type=text], textarea {
width: 95%;
font-size: $font-size-larger;
color: $text-blue;
margin: 5px 0;
}
input[type=text] {
@include height($font-size-larger*1.3);
}
}
#id_ack_link {
border: none;
cursor: default;
}
.fund_options {
a.fakeinput {
font-size: $font-size-header;
margin: 10px auto;
float: left;
line-height: normal;
}
input[type="submit"] {
float: left;
}
div {
width: 50%;
float: left;
ul {
background: $pale-blue;
}
&.highlight {
ul {
border-color: $medium-blue-grey;
background: white;
}
color: $dark-green;
}
}
ul {
padding: 5px 10px 5px 15px;
border: solid 1px $blue-grey;
margin-right: 15px;
list-style-type: none;
li {
margin: 5px 0;
}
a {
color: $medium-blue;
}
}
}
#authorize {
&.off {
display: none;
}
border: 3px solid $blue-grey;
@include one-border-radius(5px);
margin-top: 10px;
padding: 10px;
div.innards {
input[type="text"],input[type="password"] {
font-size: $font-size-larger;
line-height: $font-size-larger*1.5;
border-width: 2px;
padding: 1% 1%;
margin: 1% 0;
color: $text-blue;
&:disabled {
border-color: white;
}
&.address, &#card_Number, &#id_email {
width: 61%;
}
}
label {
width: 31%;
float: left;
line-height: $font-size-larger*1.5;
font-size: $font-size-larger;
border: solid white;
border-width: 2px 0;
padding: 1% 2% 1% 0;
margin: 1% 0;
text-align: right;
}
.form-row span {
float: left;
line-height: $font-size-larger*1.5;
font-size: $font-size-larger;
margin: 1%;
padding: 1% 0;
}
}
.cvc {
position: relative;
z-index: 0;
}
#cvc_help {
font-style: italic;
float: none;
font-size: $font-size-default;
color: $link-color;
cursor: pointer;
}
#cvc_answer {
display: none;
z-index: 100;
border: 2px solid $blue-grey;
@include one-border-radius(5px);
margin: 1% 0;
padding: 1%;
width: 46%;
position: absolute;
top: 90%;
right: 0;
opacity: 1;
background-color: white;
img {
float: right;
margin-left: 5px;
}
}
}
.payment-errors {
display: none;
@include errors;
@include one-border-radius(16px);
width: auto;
margin: auto;
}
span.level2.menu.answer {
border-left: solid 7px $pale-blue;
a {
font-size: $font-size-larger;
}
}
#anonbox {
margin-top: 10px;
background: $pale-blue;
float: left;
width: 48%;
padding: 1%;
&.off {
display: none;
}
}

234
static/scss/variables.scss Normal file
View File

@ -0,0 +1,234 @@
/* variables and mixins used in multiple less files go here */
$text-blue: #3d4e53;
$medium-blue: #6994a3;
$medium-blue-grey: #a7c1ca;
$pale-blue: #edf3f4;
$green: #8dc63f;
$call-to-action: #8dc63f;
$dark-green: #73a334;
$dark-blue: #37414d;
$blue-grey: #d6dde0;
$bright-blue: #8ac3d7;
$alert: #e35351;
$orange: #e18551;
$yellow: #efd45e;
$image-base: "/static/images/";
$background-header: "${image-base}bg.png";
$background-body: "${image-base}bg-body.png";
$background-booklist: "${image-base}booklist/bg.png";
$font-size-default: 13px;
$font-size-larger: 15px;
$font-size-header: 19px;
$font-size-shout: 22px;
$link-color: #6994a3;
//== Colors
//
//## Gray and brand colors for use across Bootstrap.
$gray-base: #000;
$gray-darker: lighten($gray-base, 13.5%); // #222
$gray-dark: lighten($gray-base, 20%); // #333
$gray: lighten($gray-base, 33.5%); // #555
$gray-light: lighten($gray-base, 46.7%); // #777
$gray-lighter: lighten($gray-base, 93.5%); // #eee
$brand-primary: darken(#428bca, 6.5%); // #337ab7
$brand-success: #5cb85c;
$brand-info: #5bc0de;
$brand-warning: #f0ad4e;
$brand-danger: #d9534f;
//** Link hover color set via `darken()` function.
$link-hover-color: darken($link-color, 15%);
//** Link hover decoration.
$link-hover-decoration: underline;
$font-size-base: 14px;
$font-size-large: ceil(($font-size-base * 1.25)); // ~18px
$font-size-small: ceil(($font-size-base * 0.85)); // ~12px
//** Unit-less `line-height` for use in components like buttons.
$line-height-base: 1.428571429; // 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: floor(($font-size-base * $line-height-base)); // ~20px
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
$padding-base-vertical: 6px;
$padding-base-horizontal: 12px;
$padding-large-vertical: 10px;
$padding-large-horizontal: 16px;
$padding-small-vertical: 5px;
$padding-small-horizontal: 10px;
$padding-xs-vertical: 1px;
$padding-xs-horizontal: 5px;
$line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome
$line-height-small: 1.5;
$border-radius-base: 4px;
$border-radius-large: 6px;
$border-radius-small: 3px;
//== Buttons
//
//## For each of Bootstrap's buttons, define text, background and border color.
$btn-font-weight: normal;
$btn-default-color: #333;
$btn-default-bg: #fff;
$btn-default-border: #ccc;
$btn-primary-color: #fff;
$btn-primary-bg: $brand-primary;
$btn-primary-border: darken($btn-primary-bg, 5%);
$btn-success-color: #fff;
$btn-success-bg: $brand-success;
$btn-success-border: darken($btn-success-bg, 5%);
$btn-info-color: #fff;
$btn-info-bg: $brand-info;
$btn-info-border: darken($btn-info-bg, 5%);
$btn-warning-color: #fff;
$btn-warning-bg: $brand-warning;
$btn-warning-border: darken($btn-warning-bg, 5%);
$btn-danger-color: #fff;
$btn-danger-bg: $brand-danger;
$btn-danger-border: darken($btn-danger-bg, 5%);
$btn-link-disabled-color: $gray-light;
//** Disabled cursor for form controls and buttons.
$cursor-disabled: not-allowed;
.header-text {
display:block;
text-decoration:none;
font-weight:bold;
letter-spacing: -.05em;
}
@mixin border-radius($topleft, $topright, $bottomright, $bottomleft)
{
-moz-border-radius: $arguments;
-webkit-border-radius: $arguments;
border-radius: $arguments;
}
@mixin one-border-radius($radius)
{
-moz-border-radius: $radius;
-webkit-border-radius: $radius;
border-radius: $radius;
}
.panelborders {
border-width: 1px 0px;
border-style: solid none;
border-color: #FFFFFF;
}
@mixin navigation-arrows($x, $y)
{
background:url($background-booklist) $x $y no-repeat;
width:10px;
height:15px;
display:block;
text-indent:-10000px;
}
@mixin supporter-color-span($hex, $color)
{
$url: %("%sheader-button-%s.png", $image-base, $color);
background:$hex url($url) left bottom repeat-x;
}
.roundedspan {
border:1px solid #d4d4d4;
@include one-border-radius(7px);
padding:1px;
color:#fff;
margin:0 8px 0 0;
display:inline-block;
> span {
padding:7px 7px;
min-width:15px;
@include one-border-radius(5px);
text-align:center;
display:inline-block;
.hovertext {
display: none;
}
&:hover .hovertext {
display: inline;
}
}
}
@mixin height($x)
{
height:$x;
line-height:$x;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.actionbuttons {
width: auto;
@include height(36px);
background: $call-to-action;
border: 1px solid transparent;
color: white;
cursor: pointer;
font-size: 13px;
font-weight: $btn-font-weight;
padding: 0 15px;
margin: 5px 0;
}
@mixin errors()
{
@include one-border-radius(16px);
border: solid $alert 3px;
clear: both;
width: 90%;
height: auto;
line-height: 16px;
padding: 7px 0;
font-weight: bold;
font-size: 13px;
text-align: center;
li {
list-style: none;
border: none;
}
}
@mixin clickyarrows()
{
text-indent:-10000px;
font-size:0;
width:15px;
height:22px;
display:block;
position:absolute;
top:45%;
}

76
vagrant/Vagrantfile vendored
View File

@ -56,20 +56,41 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
#aws.instance_type="t1.micro"
aws.instance_type="m1.small"
aws.region = "us-east-1"
aws.availability_zone = "us-east-1c"
# aws.region = "us-east-1"
# aws.availability_zone = "us-east-1c"
# 2015.05.05
# aws.ami = "ami-d8132bb0"
# 2016.03.01
# aws.ami = "ami-03dcdd69"
# 2017.03.17
# Trusty 14.04
aws.ami = "ami-9fde7f89"
# aws.ami = "ami-9fde7f89"
# put into just security group
aws.security_groups = ["just"]
# aws.security_groups = ["just"]
# FEF
aws.instance_type="m1.small"
aws.region = "us-east-1"
aws.availability_zone = "us-east-1a"
# 2017.03.17
# Trusty 14.04
# aws.ami = "ami-9fde7f89"
# 2017.11.22
# Xenial 16.04
# hvm:ebs-ssd 20171121.1 ami-aa2ea6d0
aws.ami = "ami-aa2ea6d0"
# put into just security group
# regluit-pub-a
aws.subnet_id = "subnet-a97777e0"
# SSHAccesss, just_ec2
aws.associate_public_ip = true
aws.security_groups = ["sg-93aed0ef", "sg-f6a1df8a"]
aws.tags = {
'Name' => 'please_vagrant'
'Name' => 'please_vagrant'
}
override.vm.box = "dummy"
@ -85,7 +106,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
node.vm.network "private_network", type: "dhcp"
#node.vm.network "private_network", ip: "192.168.33.10"
node.ssh.forward_agent = true
node.vm.provision 'ansible' do |ansible|
@ -122,13 +142,23 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
aws.instance_type="m1.small"
aws.region = "us-east-1"
aws.availability_zone = "us-east-1c"
aws.availability_zone = "us-east-1a"
# aws.ami = "ami-d8132bb0"
# 2017.03.17
# Trusty 14.04
aws.ami = "ami-9fde7f89"
# aws.ami = "ami-9fde7f89"
# 2017.11.22
# Xenial 16.04
# hvm:ebs-ssd 20171121.1 ami-aa2ea6d0
aws.ami = "ami-aa2ea6d0"
aws.security_groups = ["just"]
# aws.security_groups = ["just"]
# regluit-pub-a
aws.subnet_id = "subnet-a97777e0"
# SSHAccesss, just_ec2
aws.associate_public_ip = true
aws.security_groups = ["sg-93aed0ef", "sg-e0b1b59f"]
aws.tags = {
'Name' => 'just_vagrant'
@ -184,13 +214,23 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
aws.instance_type="m1.small"
aws.region = "us-east-1"
aws.availability_zone = "us-east-1c"
aws.availability_zone = "us-east-1a"
# aws.ami = "ami-d8132bb0"
# 2017.03.17
# Trusty 14.04
aws.ami = "ami-9fde7f89"
# aws.ami = "ami-9fde7f89"
# 2017.11.22
# Xenial 16.04
# hvm:ebs-ssd 20171121.1 ami-aa2ea6d0
aws.ami = "ami-aa2ea6d0"
aws.security_groups = ["just"]
# aws.security_groups = ["just"]
# regluit-pub-a
aws.subnet_id = "subnet-a97777e0"
# SSHAccesss, just_ec2
aws.associate_public_ip = true
aws.security_groups = ["sg-93aed0ef", "sg-e0b1b59f"]
aws.tags = {
'Name' => 'just2_vagrant'
@ -250,7 +290,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# aws.ami = "ami-d8132bb0"
# 2017.03.17
# Trusty 14.04
aws.ami = "ami-9fde7f89"
# aws.ami = "ami-9fde7f89"
# 2017.11.22
# Xenial 16.04
# hvm:ebs-ssd 20171121.1 ami-aa2ea6d0
aws.ami = "ami-aa2ea6d0"
aws.security_groups = ["web-production"]
@ -312,7 +356,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# aws.ami = "ami-d8132bb0"
# 2017.03.17
# Trusty 14.04
aws.ami = "ami-9fde7f89"
# aws.ami = "ami-9fde7f89"
# 2017.11.22
# Xenial 16.04
# hvm:ebs-ssd 20171121.1 ami-aa2ea6d0
aws.ami = "ami-aa2ea6d0"
aws.security_groups = ["web-production"]

View File

@ -44,7 +44,14 @@
migrate: "{{do_migrate | default('true')}}"
sudo: yes
gather_facts: False
pre_tasks:
- name: Install python for Ansible
raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
register: output
changed_when: output.stdout != ""
tags: always
- setup: # aka gather_facts
- name: check apt last update
stat: path=/var/cache/apt
register: apt_cache_stat
@ -52,12 +59,13 @@
apt: update_cache=yes
when: ansible_date_time.epoch|float - apt_cache_stat.stat.mtime > 60*60*12
tasks:
# add repo to get latest version of python 2.7
- name: add-apt-repository ppa:fkrull/deadsnakes-python2.7
apt_repository: repo='ppa:fkrull/deadsnakes-python2.7' state=present update_cache=true
when: class in ['please', 'just', 'prod']
# - name: add-apt-repository ppa:fkrull/deadsnakes-python2.7
# apt_repository: repo='ppa:fkrull/deadsnakes-python2.7' state=present update_cache=true
# when: class in ['please', 'just', 'prod']
- name: do apt-get update --fix-missing
command: apt-get update --fix-missing
@ -66,6 +74,7 @@
apt: pkg={{ item }} update_cache=yes state=present
with_items:
- python2.7
- python-pip
- git-core
- apache2
- cronolog

View File

@ -1,132 +1,136 @@
$ANSIBLE_VAULT;1.1;AES256
65633664633266336236373164373930656232376137326263363834343462363038633563356361
3437323964313539323466316435643937326436623237370a353331616530653562303361353233
39323837646538613263396461353361356332623362343630323632343036666661306639363936
6461623063626266610a343061313861643961316538373866663236623139386638363936313333
39633931623337353939376634383038656239626530363166663437653166356165313435383333
62393938623161663638383064663062323834613062386363393665363333323833653030333237
36316338346633633930643931313862376462303165616663666530333733646632343532313564
38396439323964363637396230326337323761643264633531653563613434326539333236373138
61663438353238623737343465313234313136656338373834306432363539363234636533316335
62643634356133333037373265636130356638666437343031303864383533343330383565323866
66376663393032306330653732376263653132636564363065613738616535656236663030333366
34653566633331613861636466613138353536396566626538383037653730353461633735646165
62373962313366653064363532303134623938393637623664323331313765383066326134653936
31653738383830363163366138396137623934363463373664363536613466386565346466643832
30333834353162373032393963316662323839376162333038363635613563343331363037313336
66303535636264646635666435336335356663376133663265363036666639663066313633343064
36653961333165626632313866393161396432366563313137343762373166643762316436656233
64636465313135356365363264326561303838386561353863353766623866636465633431396265
32333234353166613536383437653539316161373032326533396338646361336132643463393934
38616239333639616131386362373735313161306466386636326161373061653061643133356535
63396461376231323936643533616337656634333037366563306635373034633666373739323939
35333237623935346631326436653233643330656233643733373530316466323365663363623363
38383238613836633465316237356434323035393036386664383366623366353830633530613030
36303363346635633338636463386436653837306433636165623037613266366330333038363465
63346634343361396533663134323062396635623161633563613562383464623061393030636265
38656439613461393462646337353933333635323336303137373165626636666530323363313738
62356133366434623065653439633265393462363839383639303662613439346362633839356431
34383862373065316331643736386433363362313934346237393639666666313163353937343063
30353137643961303163613161313134343034383762363736623431613537336164653333323134
33356362366366363134333933383931626638656534323138643465303139356237373930303966
64373632646331356161643336333562613538633332353736636337613533373464663866346363
38633665663465653632383466353134646432663830383165663763323935626635653630373362
32656463306561363061626365373134393430326236646165643134323562626563633034363633
32343533376534366563393931356132366531353439656534643765653138666139656637653837
30656266343837383534643963646263373261326363316330656333633163346662306639343735
37333533343862373364396339366633343935633632336339643561363430653137366565386637
38326136353230636134313937636166383338366331636331633431313231613762383064616330
65643166303637643636376132353034653661343165333665633334326137623331616531626261
62333565653839353064343635636235316564353530313233303365636132656363653439643666
62336266303637306639306234303032373931383831306266346665663333303363626462656562
35613839386631663664646466666537313939383237616562313234613530363262303764353532
62373531346236396336386136336137623731306665383233376665373861376238346662323363
65633564663138623630323733396238653563313064326134383233373664326363323737326366
64356532303934306132383065343063343837313465336366623433376533306337383433393231
33343536303337313837666566386231396666383135396134363530323834623036323836613234
64363239323431636533393963356534643930396634633934326161383732656336383238333139
64666630396662633330373066323639396364616138633361323435643835646439393137353638
63633263383638633064326433343733366332363436653931393064336264383435353630373834
30313064636563636334633462393236636534666630326664346365663739336639306566343537
38376631623538613330326165636239653930366666343266636261326163666332616637663730
30663035333338316164333738613538366635323539356138333064656236646238303335663331
39613938373466356432396431626130353438643463376463313839653462316461373334363938
65613334306635346362623762336566626632323132383766383539666361383437386663623231
63343764333933623430656533373865666637313562303063386130616466303033666562626235
61323337363839393937663236353662363939643632373466643362386137333034383532633436
62326537643839626565343730336464643539313664616663373966353763636361373037326539
66303865323835343934663265343265343261306130306530613763313931313035363839346163
38653031383932326336616564383639643436656338653466663161383439383136316430363038
63393037343261396164636561653933346130323931383161663264356465376431386330646130
31656230646561663364386339633334353866663130393762653263353233303366396265633761
62366336643936373231626462343330383836653832303330343466313236336339383666623463
33303164646334623162396335376637643833663961323934336631643838636530366338313364
66616639636139353666663939663135353831663366383535663733323336323465306465616338
61313535633266636466616362626538393430393162353761353136393761643738393062613264
36346538633465636537613764643062353266326533343338363330336432323966646436386339
64643439353032636632656263383231333731663235623763373066383662636539613531623638
31343666386463623335346461653030616233633937313361663231623138316131666439326662
33313137393730373862316334666231336232303465386637343661373331323431623335353233
63376433666564383661303930353066653663326562326534383633336637306431323632626331
39373861303134646137646532653734616462626138313938613463383137663935346132333064
61613539326366346537313561343635303838643261303362363065313134613131633165313039
39663431316562643865346333613430393930303236623561373031346630313030613563663438
63376531393630373030313337616134386262393532383964396262666461613263333065613232
36633634346363373637306633663638366434313033356563373436626534316133663862343430
34303261376565316630623065343030616133613136333238303936343532306334306138373531
64643431353932343839323232663133343631623835633264386562326631646461333931636565
66633266626433343431623532656661616161386163313533396165333966613865316238626463
34613136616462343862336262616364616265616466356563343133363534376161326532626336
35666362343364336365643538643862646538396331623062313762663536363864613331376462
66303832363965376163363966313433346532303131383035663637323031373933636565393333
64643464656365643434613161313239623039333036623637353662626161376661336335636436
39353131383361336535313539653963383836663730643565373962343764343562653932656566
31613033353739323134303435396236616537623733363663363436393534616635323761333233
64393936626136626437623165393530623566383365353530613166343263323535383238343962
36323630313931336233653530623133336564326232663762313735303134633362363964393662
31303365313865343163616635356661353062386237316639366339373931623865376363613333
34653935643561336665663264643264303262633361396230633631346436643862313432666161
30626364623539633030366466393334313761383935373237306330393737306532363063363131
63663832316530666134613865663464303465356536383039313437626665653534353332393463
37373034353935303930623138616262616261613362613338613436316534373637316263396265
34653866646164333737383036326563646363653832626132616431346665623938313932366437
61323237613863653635366265646461343035333838643064323834346164366531346362326534
62383930646133656433663736383430333034333937373639353235616636656234633936613564
66656631643139393063326233393637306139373962636631333862386562366339323966343233
39343333653535386137373066363131363732333735313665313738306265353533636336333437
34363734393430646563373864633330363831623739333566373063323866313939303834306263
62383132386263343631613464623037343265366163393837353665306530636366626465636433
32383961366232346330306334333734663834366163336531373366353361613861396430356135
66383339376665336231616138343831626232623338656430653539336430636236333763666565
63613630636462346238333938306333633832346232386231663466643437633361663831363636
31623161363935323035316635383131636435626166376532343661666135383931626336646336
33316164376233343637306231323365313262653831353838373739323234623836666438356461
34386533346635613064363866636235616337623632653037653439656535626433626538626663
36643138396438633964353439636637373562373839326132653039663533626565393361643064
66613235313461613037393064313637656366626639313631643136363766366162333863323461
36623363393563346135666238623161616336633864333534643662306238663861643464313461
64393264396130373030323138653565356239333932356166366232383531356435623537353533
61313030343563636230316164633361396639373334393631326134656164643538656135636432
65353761393430316363343439353636383966316438356264303034616265626463323661343639
38333761343130613566383931336530326536323537326330656231653634303737303361633737
34303739633362616665373831643261643333313634663266306366653861653065313337643863
64353961633537616666643135613436666561333930396533346431636331316336633233633764
35643439613063343965636434353938333831356138653131373133343230393866313462633831
66376261656363383430373438353265393132323831346565353036356139636466346436646231
63623861633335313139333336663031666338626366333965333138363134623332373032383935
63633235303330383737373661623537663138613036386564356334333466386133353639326334
35646233333837346536343637633664626662333363643663633866363065663033376537346131
32393161306566663333353034393135646533356365313563383631613939663837363231333930
65633739633432616538613531646361333866616362373463303665646133313031383461373361
31333061626138626130353238336134663266353030326232646632663135346337306232646461
39653338613439343531656264323834376334366265653439303134353138666438316631346336
63343730336433336338323732623062363963383061333335643337363931663663613363636532
63313961386661336536313133316430656331343731623338393235313234376331393634336663
64393231343230346130386166343864643061643538373636383939333333613165343431313662
35386234633263356564613861653734356362643836653235373638623439316634653030333936
33333761653463363632373539343835323739636661636435343761346630316362313061333562
61636463353165653464626563363237393964386163326661653432313065373030653533306132
32363934386131346561343462336630303237626132323833303864396265616562396631653439
32376465393564616562346663306164313738353333663438653662333662393465613230396266
33626535663731323039346262373735383562613134346632353432333662653533333338653135
363439633236383364326138626433623730
32636230316261363062326338363338386265623431633263313431373432616230646334313062
3836363231306431333533326538386636383561343565660a663539313233313537383136613434
35303864346335386338353633366338383134623139306338363065316232323463626264636335
6134346134386531370a353234353061386364306232663231646362653838383539393931346466
31353966373761653365363464636336326464623736343630343136353066353331393731646236
38646663653862363435343338623731373532303261313661393538643761346638376238616530
63363238613961316332363733653837343662376433636261336565316231393533383038653936
31323664363839656561346431333166623263383737363964396564663164393439613634663636
31346566626332363963656434333634353233356533323863623939383730333061613639383539
39303530373033366635303531353662366339373937313663623138323836353064326637613737
36313430363465656538666534333364366663316537323639363239353561306530316130303133
32356636326130346630376134393264316461383464336261313734656333653036636534303961
35663631643132646334393539663365326664343636326163653161653137646663663466363035
63666530353438636231383332333235626431636531643036373935666132633433643838633132
35386561343366336535396538343630613632623938323963366562613733626238356334383432
32656536666539313462643738656531373438623631393230646661613731616466613439313038
63333538643662343837666239376430333562373065316266336131343532646239346634663263
33323235663965326439343534396461376236373962666530396466616265346266343935633566
39396432343163303666343762643934323461313935343531626432353762623236326162643939
64663831653962626633356430633232393737343237323134616333393436313337666165366634
32623861626630303137656331643738643562323531336564383466666532303636383538313630
30623763333430343762393164353131373733336230323432313266356536383361333537343937
32636532613134303234313531646436666633613431616338663263316337303635376361306338
39333764376136363664303165393938646366333832333261313337623337303138616661663362
61343663376135613036313736653964343963616439636239663261393037323439323061366339
30323130333431323534643635663531366266393062363362623930633139366164326236636232
65363562393063666334613136306166616232313333633337373965383631386165613261363662
65316466613465313332626139363365383262653731633162366639323239346239363930363761
30663839663830363462663136336432346233383934383538343334623335313033386462393135
64326633643965643366666331336362623137626166616237303464323135643634653662303335
30386237313338333532666236393637663131313939393939666265303264666466363962653166
62626231616463346134346337323132316331313862336665613261626532666130356433336265
64633136666433653532333964393861316235646434623733653961343034356165663065666330
39376239343462396361323731376632323936396332393461646238623531323332623937323862
30373932656463333161306336323166393964376637366236393137323432383432313736323464
61666634306564343865646438346664363563643531623064346234316533306431303735323866
32623762643230366631396431393862623363343331626136336131386636336439376661376235
31643761336332303933613936393365343035396332376663656632343739343264646565646466
37353031366661333134313134333137343530623235616239386439643539346636656362616333
31646535323362306139356332333536653839646234316231346535343538336432363261353534
36373634346131346132306366336136623163623039623231363739653730346135613631346335
36303036383335623635383363383733353135653665643239613939333931386539386161623762
33613133303763303431666564633031323162336230653661383730336664353233353530323237
33663466303163363165326234306561383634316130386637363866323732623233396363343065
37303862383764616231356234666633366337363961326339613031346135636633616566393337
65353339386439393965313866363533633633656463643364393134653339656135353436316139
36313663386566666133626466396539366566643130363439333732663465316162633132646234
62363139666331363962366430383233333731383031633437623166386337653436376639616435
35363561373162396362613136353537623935613136373633303965393533336461626366646366
61373036663837323839356163613538613032346437316433663534396333313966616231623964
37633735306561303861666237616438663232653866623637616561323365373966326530623161
31633139386533333661373734333232623961613464646130323735643232333666633832336334
38373066306238353866346230376265633839323831383563346438393436633430316636333434
37653937333365633332613735363032376536666230643462646435613533363166303930653566
65643634633936653530313662363139303133383933333866366237646435323131613131663564
32616532633965666234383766613534613830396465333933393963393238663535316366646630
31316234333934353035623661636230376566313338653832353661646135663037666333303464
33623732353136376136616131343430336665636331663665373134353634343233383738393066
66313635666538663861633563323032356465343465393933666631643230663661626430383630
62326436343831666462613031333266663139333838366630346135303633386632306235643165
34353439646163613966656434313930366136366132653039346663343963333135363965663664
64376462356163336430313565643538383732623134633135396636656161323465616238376163
38313931333062666137376338613732353634393063396438363239316662623535663533376664
63343262653232343039373266346330623466326462636632336565383536323862323661663936
31663466353339313339343862653134323135333339646634303633346436323930396137343162
36333336313331366432373165376633623137303535356462353866373865363533383065656238
31383039643833313837373139366366663737656530633164393838313339323665386265306566
63383632393337613434393338346463363835363164393630653964383832653466303164306263
30633462316231323533633364623962343361663235626236373738356133623461316665366332
65366166333066366638323732303362633735333439396661306162646534396564386639613533
39303735316234356533383733383435613766326639336533663238333465653437323638623537
35363962636433616130666537616664343462313863313735343237663034323564353961383662
30386461386532323065373663363631613134663038303437373635333632643261666330613230
65613335303335326163396432313837323866623639616632383437613939623132613864396432
30656631313466656665623433643038366332656165383532343162386465313565303532396233
36646430623935303264346466376661386130323063303630323662306565313730663537333436
61356235343939386337643065636238346531653931373531376464373862316236656161396632
66663331343365636266623661373637383262383264313931343162666231353732366231646634
32333166353231346662373062663633383961376633316633396464386331663962313435666131
39633835333035636330376239616130666434393735393561623263646638303966636139633732
39363165613732393535623365316461303934343233663064306163316130326161663636373861
35353638633833613730336565383862633232386436356462313466323835326530666430663630
39666164376666663638643133343439336136613432323861653135613330333661366539316262
31386664623664383334633033303439313663323832383862656333366337323564613532616336
64313565333638633733613132393565316463393130336331383865643061313435656135613362
66316664623938616266323738633565386561396366346461653532613039316635303133303737
64303037653431626631366361373364343834306231393066343736386662633935393162646530
38653236353937393139613330643066386638303536373333306530343933366335336263636666
32653138616363666630613431386239313030306161653565633638373632313139323832393434
65333137636663373532646130313932306436666363366335316330396431323363346437613561
33343036333136616330343364643833313465326432346535313232373066633763656161306138
38383038353037376330643934393665663966396461633462643735643233323165373830633439
30383233616466613563333666393734326366373462336533356165636464376432326464643738
35336664313161663232303130303334353431363134303936623335323863633038373861363931
65363764316339333065656466313337373135626430326266353432633637346364393330306331
37376464653333636135363761346430336238666261323866353739376231643934336238623266
66303962313166363133643164316466396662623233363539363332633433373762313237613532
65623264323961346661326639366665303939363261363436633266313165323361333135353530
34376266656364323633396533326564613331333731326563343735343463303731303939613538
62626361616265643563643232356139316633363537373863646166653166626536353630323734
37313131656435313866333239393531313162333263366233373930333263643035643061666332
63386639653737336262653234346466326137376234323539393466363363663231636131656262
65323034613235326664356436343339633832383966336436323038383335323861356638306235
36376163383364323834356130393664356566313464326566336461656566326135326163376533
38343365323862346139336336326630376330393236636363653761626337313132393739323165
62313462666232326562366264666165613234393036653231636639663061326266303839306236
38313331386536656334323732396532303565383431313631666632373934623634313463306533
65306638616466336266646464323539376637336232363738613462646264626430323266663830
37303162656666383336633264393233306365316164366265626637303961646262663066306164
36306534326461313866316532633561623862383863626162396564316265323034643661363066
33663166393866303839393361623031613665636637613035366463643166313662373635333737
32363230346234313965353833373339646463313166343736313563323434316134666132646566
63663764663764393865636365653336323664356166343032373138356630326461346632386230
63373864643565323430666462386464653239666362363134663332636233656631386232366638
62316139343266353065613632393437393138623337366661643362323235316332666261313830
63393961636336653639656236643464333765646261393834383865346233643361666461623437
65316137633833613666626561313630393233376666626330353264333536313932646232633938
37613163616531653830326334353236353062376563343961646163313766663033643234633135
35666531353432666662386663613534396136666434363034666361383837316532393432616639
36316232353565396539646636326638383164356531363136666365313335313038343237643964
30336137616436323332383035323432333563373732613762643137656532323035646338343430
35333539333433353539306236626131353330663466313564376231646632663361653335343439
34353334653531663634656665346536623861656564353938313236383434346137396364316665
36316434363830373337343530386135623234363435353964346534643637346430616336383561
64353332353263613530323363366163616633323232333561663038656338643934303839633161
61356630353463393138343038303732653461346535383462643066323733373863343763346337
39333763333836356531373065376161396431396663663037636462616564323138366566333633
39643437653637303333316634386462323533363862393463623133626530396239396533313133
36663266303065373234636231383162373231623936363863363466636331653961336639373337
34616635613337666235376133346233623462626331323533643230646665366161653334636565
31313738663838366235633966373665383865393536396333353066333930646335376561383732
66393466653664393933303066373765383730666235326539653032303935373734663934353531
64356466613035363464643463386135316438353131383935303637333636383164633563666637
63333266343234656437363436313762316364623831376135653737316431363864363437366665
323963393638666636623065313162323939