Allow submissions with short enough titled to be made into T-shirts thru spreadshirt.

This commit is contained in:
KeyserSosa
2009-07-15 11:03:15 -07:00
parent c0b98e0c3e
commit 9c4ebbbdfd
44 changed files with 488 additions and 3 deletions

View File

@@ -23,7 +23,7 @@
# Jacascript files to be compressified
js_targets = jquery.js jquery.json.js jquery.reddit.js reddit.js
# CSS targets
css_targets = reddit.css reddit-ie6-hax.css reddit-ie7-hax.css mobile.css
css_targets = reddit.css reddit-ie6-hax.css reddit-ie7-hax.css mobile.css spreadshirt.css
SED=sed

View File

@@ -144,6 +144,13 @@ agents =
feedback_email = abuse@localhost
# t-shirt stuff
spreadshirt_url =
spreadshirt_vendor_id =
spreadshirt_min_font = 42
spreadshirt_max_width = 620
spreadshirt_test_font =
[server:main]
use = egg:Paste#http

View File

@@ -99,6 +99,8 @@ def make_map(global_conf={}, app_conf={}):
action = 'details', title=None)
mc('/traffic/:article/:title', controller='front',
action = 'traffic', title=None)
mc('/shirt/:article/:title', controller='front',
action = 'shirt', title=None)
mc('/comments/:article/:title/:comment', controller='front',
action = 'comments', title=None, comment = None)
mc('/duplicates/:article/:title', controller = 'front',

View File

@@ -32,12 +32,12 @@ import r2.models.thing_changes as tc
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
from r2.lib.utils import query_string, to36, timefromnow, link_from_url
from r2.lib.wrapped import Wrapped
from r2.lib.pages import FriendList, ContributorList, ModList, \
BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage, \
ClickGadget
from r2.lib.pages.things import wrap_links, default_thing_wrapper
from r2.lib import spreadshirt
from r2.lib.menus import CommentSortMenu
from r2.lib.normalized_hot import expire_hot
from r2.lib.captcha import get_iden
@@ -1460,3 +1460,19 @@ class ApiController(RedditController):
wrapped = wrap_links(link)
wrapped = list(wrapped)[0]
return spaceCompress(websafe(wrapped.link_child.content()))
@validatedForm(link = VByName('name', thing_cls = Link, multiple = False),
color = VOneOf('color', spreadshirt.ShirtPane.colors),
style = VOneOf('style', spreadshirt.ShirtPane.styles),
size = VOneOf("size", spreadshirt.ShirtPane.sizes),
quantity = VInt("quantity", min = 1))
def POST_shirt(self, form, jquery, link, color, style, size, quantity):
if not g.spreadshirt_url:
return self.abort404()
else:
res = spreadshirt.shirt_request(link, color, style, size, quantity)
if res:
form.set_html(".status", _("redirecting..."))
jquery.redirect(res)
else:
form.set_html(".status", _("error (sorry)"))

View File

@@ -131,6 +131,13 @@ class FrontController(RedditController):
return DetailsPage(link = article).render()
@validate(article = VLink('article'))
def GET_shirt(self, article):
if g.spreadshirt_url:
from r2.lib.spreadshirt import ShirtPage
return ShirtPage(link = article).render()
return self.abort404()
@validate(article = VLink('article'),
comment = VCommentID('comment'),
context = VInt('context', min = 0, max = 8),

View File

@@ -118,6 +118,7 @@ menu = MenuHandler(hot = _('hot'),
related = _("related"),
details = _("details"),
duplicates = _("other discussions (%(num)s)"),
shirt = _("shirt"),
traffic = _("traffic"),
# reddits

View File

@@ -88,6 +88,7 @@ class Reddit(Templated):
enable_login_cover = True
site_tracking = True
show_firsttext = True
additional_css = None
def __init__(self, space_compress = True, nav_menus = None, loginbox = True,
infotext = '', content = None, title = '', robots = None,
@@ -624,6 +625,9 @@ class LinkInfoPage(Reddit):
if c.user_is_sponsor:
if self.link.promoted is not None:
buttons += [info_button('traffic')]
if len(self.link.title) < 200 and g.spreadshirt_url:
buttons += [info_button('shirt')]
toolbar = [NavMenu(buttons, base_path = "", type="tabmenu")]

145
r2/r2/lib/spreadshirt.py Normal file
View File

@@ -0,0 +1,145 @@
from pylons import g, c
import sha, base64, time, re, urllib, socket
import ImageFont
from r2.lib.wrapped import Templated
from r2.lib.pages import LinkInfoPage
from r2.models import *
from httplib import HTTPConnection
from urlparse import urlparse
from BeautifulSoup import BeautifulStoneSoup
colors = (("black",2), ("white", 1), ("navy",4), ("heather",231), ("red",5))
sizes = (("small",2), ("medium",3), ("large",4), ("xlarge", 5), ("xxlarge",6))
articles = {"women":
dict(black = 4604645,
heather = 4604654,
navy = 4737035,
red = 4604670,
white = 4604694,
),
"men" :
dict(black = 4589785,
heather = 4599883,
navy = 4737029,
red = 4589762,
white = 4589259,
) }
spreadshirt_url = urlparse(g.spreadshirt_url)
try:
test_font = ImageFont.truetype(g.spreadshirt_test_font,
int(g.spreadshirt_min_font))
except IOError:
test_font = None
word_re = re.compile(r"\w*\W*", re.UNICODE)
def layout_text(text, max_width = None):
if test_font:
words = list(reversed(word_re.findall(text)))
lines = [""]
while words:
word = words.pop()
w = test_font.getsize(lines[-1] + word)[0]
if w < max_width:
lines[-1] += word
else:
lines.append(word)
lines = [x.strip() for x in filter(None, lines)]
return all(test_font.getsize(x)[0] < max_width for x in lines), lines
return None, []
def spreadshirt_validation(s):
t = str(int(time.time()))
return t, base64.b64encode(sha.new(s+t+g.spreadshirt_vendor_id).digest())
def shirt_request(link, color, style, size, quantity):
article = articles.get(style, {}).get(color)
size = dict(sizes).get(size)
color = dict(colors).get(color)
# load up previous session id (if there was one)
sessionid = c.cookies.get("spreadshirt")
sessionid = sessionid.value if sessionid else ""
if link and color and size and quantity and article:
# try to layout the text
text = ShirtPane.make_text(link)
if text:
author = Account._byID(link.author_id)
request_dict = dict(color = color,
quantity = quantity,
sessionId = sessionid,
size = size,
article_id = article)
for i, t in enumerate(text):
request_dict["textrow_%d" % (i+1)] = t
request_dict["textrow_6"] = "submitted by %s" % author.name
request_dict["textrow_7"] = link._date.strftime("%B %e, %Y")
text.extend([request_dict["textrow_6"], request_dict["textrow_7"]])
t, code = spreadshirt_validation("".join(text))
request_dict['timestamp'] = t
request_dict['hash'] = code
params = urllib.urlencode(request_dict)
headers = {"Content-type": "application/x-www-form-urlencoded",
"Accept": "text/plain"}
data = None
try:
conn = HTTPConnection(spreadshirt_url.hostname)
conn.request("POST", spreadshirt_url.path, params, headers)
response = conn.getresponse()
if int(response.status) == 200:
data = BeautifulStoneSoup(response.read())
conn.close()
except socket.error:
return
if data:
if not data.find("error"):
session_id = data.sessionid.contents[0]
data = data.basketurl.contents[0]
# set session id before redirecting
c.cookies.add("spreadshirt", session_id)
else:
g.log.error("Spreadshirt Error:\n" )
g.log.error(data.prettify() + '\n')
g.log.error("POST and params: " + g.spreadshirt_url)
g.log.error(params)
data = None
return data
class ShirtPage(LinkInfoPage):
extension_handling= False
additional_css = "spreadshirt.css"
def __init__(self, *a, **kw):
kw['show_sidebar'] = False
LinkInfoPage.__init__(self, *a, **kw)
def content(self):
return self.content_stack((self.link_listing,
ShirtPane(self.link)))
class ShirtPane(Templated):
default_color = "white"
default_size = "large"
default_style = "men"
colors = [x for x, y in colors]
styles = ("men", "women")
sizes = [x for x, y in sizes]
def __init__(self, link, **kw):
Templated.__init__(self, link = link, text = self.make_text(link), **kw)
@classmethod
def make_text(cls, link):
fit, text = layout_text(link.title,
int(g.spreadshirt_max_width))
if len(text) > 5 or not fit:
text = []
return text

View File

@@ -91,4 +91,56 @@ div.cover {
div.popup {
position: absolute;
top: expression( ( 40 + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );
}
}
.shirt .shirt-container .red {
background-image: url(/static/spreadshirt/red.gif);
}
.shirt .shirt-container .white {
background-image: url(/static/spreadshirt/white.gif);
}
.shirt .shirt-container .navy {
background-image: url(/static/spreadshirt/navy.gif);
}
.shirt .shirt-container .heather {
background-image: url(/static/spreadshirt/heather.gif);
}
.shirt .shirt-container .black {
background-image: url(/static/spreadshirt/black.gif);
}
.shirt .shirt-container .main .caption #layout {
height: 5em;
background-image: url(/static/spreadshirt/spreadshirt-arrows.gif);
}
.shirt .shirt-container .main.navy .caption #layout,
.shirt .shirt-container .main.black .caption #layout {
background-image:url(/static/spreadshirt/spreadshirt-arrows-white.gif);
}
.shirt .shirt-container .main.red .caption #layout {
background-image:url(/static/spreadshirt/spreadshirt-arrows-red.gif);
}
.shirt .shirt-container .main .caption .byline {
background-image: url(/static/spreadshirt/spreadshirt-header.gif);
}
.shirt .shirt-container .main.navy .caption .byline,
.shirt .shirt-container .main.black .caption .byline {
background-image:url(/static/spreadshirt/spreadshirt-header-white.gif);
}
.shirt .shirt-container .main.red .caption .byline {
background-image:url(/static/spreadshirt/spreadshirt-header-red.gif);
}
.shirt .shirt-container .main .caption.big .byline {
background-image: none;
}
.shirt .shirt-container .main.navy .caption.big .byline,
.shirt .shirt-container .main.black .caption.big .byline {
background-image: none;
}
.shirt .shirt-container .main.red .caption.big .byline {
background-image: non;
}

View File

@@ -0,0 +1,161 @@
.shirt {
margin-left: 30px;
width: 600px;
}
.shirt h2 {
margin: 30px;
text-align: center;
font-size: large;
color: gray;
}
.shirt .shirt-container { margin: 10px; }
.shirt .shirt-container .left {
border: 1px solid #5f99cf;
background-color: #EFF7FF;
margin-top: 10px;
float:left;
}
.shirt .shirt-container .left h4 {
color: #336699;
margin: 2px;
}
.shirt .shirt-container .left input[type=submit] {
background-color: #5f99cf;
width: 95px;
color: white;
text-transform: uppercase;
font-size: larger;
font-weight: bold;
border-width: 1px;
border-color: black black black black;
}
.shirt .shirt-container .left input[type=radio] {
margin: 0 2px;
vertical-align: bottom;
}
.shirt .shirt-container .left input[type=text] {
border: 1px solid #AAA;
margin: 5px;
}
.shirt select { margin-left: 5px; }
.shirt .shirt-container .main.white.men {
background-image: url(/static/spreadshirt/white.png);
}
.shirt .shirt-container .main.heather.men {
background-image: url(/static/spreadshirt/heather.png);
}
.shirt .shirt-container .main.navy.men {
background-image: url(/static/spreadshirt/navy.png);
}
.shirt .shirt-container .main.black.men {
background-image: url(/static/spreadshirt/black.png);
}
.shirt .shirt-container .main.red.men {
background-image: url(/static/spreadshirt/red.png);
}
.shirt .shirt-container .main.white.women {
background-image: url(/static/spreadshirt/white-womens.png);
}
.shirt .shirt-container .main.navy.women {
background-image: url(/static/spreadshirt/navy-womens.png);
}
.shirt .shirt-container .main.heather.women {
background-image: url(/static/spreadshirt/heather-womens.png);
}
.shirt .shirt-container .main.black.women {
background-image: url(/static/spreadshirt/black-womens.png);
}
.shirt .shirt-container .main.red.women {
background-image: url(/static/spreadshirt/red-womens.png);
}
.shirt .shirt-container .main.black .caption #layout,
.shirt .shirt-container .main.black .caption .byline,
.shirt .shirt-container .main.navy .caption #layout,
.shirt .shirt-container .main.navy .caption .byline,
.shirt .shirt-container .main.red .caption #layout,
.shirt .shirt-container .main.red .caption .byline
{
color: white;
}
.shirt .shirt-container .main {
background-image: url(/static/spreadshirt/white.png);
background-repeat: no-repeat;
background-position: center center;
border: 1px solid #5f99cf;
width: 450px;
height: 350px;
margin-left: 96px;
position: relative;
margin-bottom: 75px;
}
.shirt .shirt-container .main .caption {
width: 160px;
margin-top: 80px;
margin-left: auto;
margin-right: auto;
font-family: verdana;
font-size: 9px;
text-align: left;
}
.shirt .shirt-container .main .caption #layout {
padding-left: 17px;
background-image: url(/static/spreadshirt/spreadshirt-arrows.png);
background-repeat: no-repeat;
background-position: top left;
min-height: 30px;
}
.shirt .shirt-container .main.navy .caption #layout,
.shirt .shirt-container .main.black .caption #layout {
background-image:url(/static/spreadshirt/spreadshirt-arrows-white.png);
}
.shirt .shirt-container .main.red .caption #layout {
background-image:url(/static/spreadshirt/spreadshirt-arrows-red.png);
}
.shirt .shirt-container .main .caption .byline {
margin-top: 5px;
border: none;
text-align: right;
font-weight: bold;
font-size: 4pt;
margin-left: 12px;
padding-bottom: 20px;
background-image: url(/static/spreadshirt/spreadshirt-header.png);
background-repeat: no-repeat;
background-position: bottom left;
}
.shirt .shirt-container .main.navy .caption .byline,
.shirt .shirt-container .main.black .caption .byline {
background-image:url(/static/spreadshirt/spreadshirt-header-white.png);
}
.shirt .shirt-container .main.red .caption .byline {
background-image:url(/static/spreadshirt/spreadshirt-header-red.png);
}
.shirt .shirt-container .main .caption.big {
font-family: verdana;
width: 340px;
padding: 10px;
border: 3px solid #369;
font-size: 20px;
background-color: white;
position: absolute;
top: 250px;
left: 45px;
margin: 0px;
}
.shirt .shirt-container .main .caption.big .byline {
font-size: x-small;
background: none;
color: black;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -67,6 +67,10 @@
%endif
%endif
%endif
%if getattr(thing, "additional_css", None):
<link rel="stylesheet" href="${static(thing.additional_css)}"
type="text/css" />
%endif
<link rel='shortcut icon' href="${static('favicon.ico')}"
type="image/x-icon" />

View File

@@ -0,0 +1,86 @@
%if not thing.text:
<p class="error">
${_("sorry. That title is too long to fit on a t-shirt.")}
</p>
%else:
<script type="text/javascript">
function update_shirt(input) {
$(input).parents("form").each(function() {
$(this).find(".shirt-container .main").removeClass()
.addClass("main")
.addClass($(this).find("[name=style]").val())
.addClass($(this).find("[name=color]").val());
});
}
$(function() {
update_shirt($("form.shirt input:first"));
});
</script>
<div class="shirt preload">
<div class="shirt-container">
%for color in thing.colors:
%for style in thing.styles:
<div class="main ${color} ${style}">
</div>
%endfor
%endfor
</div>
</div>
<form class="shirt" method="post" action="/post/shirt"
onsubmit="return post_form(this, 'shirt')">
<input type="hidden" name="name" value="${thing.link._fullname}" />
<h2>
${_("Your reddit headline shirt")}
</h2>
<div class="shirt-container">
<div class="left">
<h4>${_("Color")}</h4>
<select name="color" onchange="update_shirt(this)">
%for v in thing.colors:
<option value="${v}" id="radio-${v}"
${"selected" if v == thing.default_color else ''}>${v}</option>
%endfor
</select>
<h4>${_("Size")}</h4>
<select name="size">
%for v in thing.sizes:
<option value="${v}" id="radio-${v}"
${"selected" if v == thing.default_size else ''}>
${v}</option>
%endfor
</select>
<h4>${_("Style")}</h4>
<select name="style" onchange="update_shirt(this)">
%for v in thing.styles:
<option value="${v}" id="radio-${v}"
${"selected" if v == thing.default_style else ''}>${v}</option>
%endfor
</select>
<h4>${_("Qty:")}
<input type="text" name="quantity" maxlength="2" size="2" value="1"/>
</h4>
<input type="submit" name="sumbit" value="buyyit!" />
</div>
<div style="float:left; clear: left" class="status">&nbsp;</div>
<div class="main ${thing.default_color}">
%for cls in ("", "big"):
<div class="caption ${cls}">
<div id="layout${cls}">
%for text in thing.text:
${text}<br/>
%endfor
</div>
<div class="byline">
submitted by ${thing.link.author.name}<br/>
${thing.link._date.strftime("%B %e, %Y")}
</div>
</div>
%endfor
</div>
</div>
</form>
%endif