mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-27 07:48:16 -05:00
Implement a flair template management interface.
This commit is contained in:
@@ -33,8 +33,9 @@ from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
|
||||
from r2.lib.utils import query_string, timefromnow, randstr
|
||||
from r2.lib.utils import timeago, tup, filter_links, levenshtein
|
||||
from r2.lib.pages import EnemyList, FriendList, ContributorList, ModList, \
|
||||
FlairList, FlairCsv, BannedList, BoringPage, FormPage, CssError, \
|
||||
UploadedImage, ClickGadget, UrlParser, WrappedUser
|
||||
BannedList, BoringPage, FormPage, CssError, UploadedImage, ClickGadget, \
|
||||
UrlParser, WrappedUser
|
||||
from r2.lib.pages import FlairList, FlairCsv, FlairTemplateEditor
|
||||
from r2.lib.utils.trial_utils import indict, end_trial, trial_info
|
||||
from r2.lib.pages.things import wrap_links, default_thing_wrapper
|
||||
|
||||
@@ -2021,6 +2022,47 @@ class ApiController(RedditController):
|
||||
flair = FlairList(num, after, reverse, '', user)
|
||||
return BoringPage(_("API"), content = flair).render()
|
||||
|
||||
@validatedForm(VFlairManager(),
|
||||
VModhash(),
|
||||
flair_template_id = nop('id'),
|
||||
text = VFlairText('text'),
|
||||
css_class = VFlairCss('css_class'))
|
||||
def POST_flairtemplate(self, form, jquery, flair_template_id, text,
|
||||
css_class):
|
||||
# Check validation.
|
||||
if form.has_errors('css_class', errors.BAD_CSS_NAME):
|
||||
form.set_html(".status:first", _('invalid css class'))
|
||||
return
|
||||
if form.has_errors('css_class', errors.TOO_MUCH_FLAIR_CSS):
|
||||
form.set_html(".status:first", _('too many css classes'))
|
||||
return
|
||||
|
||||
# Load flair template thing.
|
||||
if flair_template_id:
|
||||
ft = FlairTemplate._byID(flair_template_id)
|
||||
ft.text = text
|
||||
ft.css_class = css_class
|
||||
ft._commit()
|
||||
new = False
|
||||
else:
|
||||
ft = FlairTemplateBySubredditIndex.create_template(
|
||||
c.site._id, text=text, css_class=css_class)
|
||||
new = True
|
||||
|
||||
# TODO(intortus): ...
|
||||
# Push changes back to client.
|
||||
if new:
|
||||
jquery('#empty-flair-template').before(
|
||||
FlairTemplateEditor(ft).render(style='html'))
|
||||
empty_template = FlairTemplate()
|
||||
empty_template._committed = True # to disable unnecessary warning
|
||||
jquery('#empty-flair-template').html(
|
||||
FlairTemplateEditor(empty_template).render(style='html'))
|
||||
else:
|
||||
jquery('input[name="text"]').data('saved', text)
|
||||
jquery('input[name="css_class"]').data('saved', css_class)
|
||||
form.set_html('.status', _('saved'))
|
||||
|
||||
@validatedForm(VAdminOrAdminSecret("secret"),
|
||||
award = VByName("fullname"),
|
||||
description = VLength("description", max_length=1000),
|
||||
|
||||
@@ -23,7 +23,8 @@ from r2.lib.wrapped import Wrapped, Templated, CachedTemplate
|
||||
from r2.models import Account, FakeAccount, DefaultSR, make_feedurl
|
||||
from r2.models import FakeSubreddit, Subreddit, Ad, AdSR
|
||||
from r2.models import Friends, All, Sub, NotFound, DomainSR, Random, Mod, RandomNSFW, MultiReddit
|
||||
from r2.models import Link, Printable, Trophy, bidding, PromotionWeights, Comment, Flair
|
||||
from r2.models import Link, Printable, Trophy, bidding, PromotionWeights, Comment
|
||||
from r2.models import Flair, FlairTemplate, FlairTemplateBySubredditIndex
|
||||
from r2.config import cache
|
||||
from r2.lib.tracking import AdframeInfo
|
||||
from r2.lib.jsonresponse import json_respond
|
||||
@@ -491,13 +492,10 @@ class SubredditInfoBar(CachedTemplate):
|
||||
# so the menus cache properly
|
||||
self.path = request.path
|
||||
|
||||
# if user has flair, then it will be displayed in this bar
|
||||
self.flair_user = None
|
||||
if c.user_is_loggedin:
|
||||
wrapped_user = WrappedUser(c.user, subreddit=self.sr,
|
||||
force_show_flair=True)
|
||||
if wrapped_user.has_flair:
|
||||
self.flair_user = wrapped_user
|
||||
self.flair_selector = FlairSelector()
|
||||
else:
|
||||
self.flair_selector = None
|
||||
|
||||
CachedTemplate.__init__(self)
|
||||
|
||||
@@ -2272,7 +2270,8 @@ class WrappedUser(CachedTemplate):
|
||||
FLAIR_CSS_PREFIX = 'flair-'
|
||||
|
||||
def __init__(self, user, attribs = [], context_thing = None, gray = False,
|
||||
subreddit = None, force_show_flair = None):
|
||||
subreddit = None, force_show_flair = None,
|
||||
flair_template = None):
|
||||
attribs.sort()
|
||||
author_cls = 'author'
|
||||
|
||||
@@ -2288,6 +2287,12 @@ class WrappedUser(CachedTemplate):
|
||||
flair = wrapped_flair(user, subreddit or c.site, force_show_flair)
|
||||
flair_enabled, flair_position, flair_text, flair_css_class = flair
|
||||
has_flair = bool(flair_text or flair_css_class)
|
||||
|
||||
if flair_template:
|
||||
flair_text = flair_template.text
|
||||
flair_css_class = flair_template.css_class
|
||||
has_flair = True
|
||||
|
||||
if flair_css_class:
|
||||
# This is actually a list of CSS class *suffixes*. E.g., "a b c"
|
||||
# should expand to "flair-a flair-b flair-c".
|
||||
@@ -2400,9 +2405,16 @@ class FlairPane(Templated):
|
||||
def __init__(self, num, after, reverse, name, user):
|
||||
# Make sure c.site isn't stale before rendering.
|
||||
c.site = Subreddit._byID(c.site._id)
|
||||
|
||||
tabs = [
|
||||
('templates', _('edit flair templates'), FlairTemplateList()),
|
||||
('grant', _('grant flair'), FlairList(num, after, reverse, name,
|
||||
user)),
|
||||
]
|
||||
|
||||
Templated.__init__(
|
||||
self,
|
||||
flair_list=FlairList(num, after, reverse, name, user),
|
||||
tabs=TabbedPane(tabs),
|
||||
flair_enabled=c.site.flair_enabled,
|
||||
flair_position=c.site.flair_position)
|
||||
|
||||
@@ -2488,6 +2500,72 @@ class FlairCsv(Templated):
|
||||
self.results_by_line.append(self.LineResult())
|
||||
return self.results_by_line[-1]
|
||||
|
||||
class FlairTemplateList(Templated):
|
||||
@property
|
||||
def templates(self):
|
||||
ids = FlairTemplateBySubredditIndex.get_template_ids(c.site._id)
|
||||
fts = FlairTemplate._byID(ids)
|
||||
return [FlairTemplateEditor(fts[i]) for i in ids]
|
||||
|
||||
class FlairTemplateEditor(Templated):
|
||||
def __init__(self, flair_template):
|
||||
Templated.__init__(self,
|
||||
id=flair_template._id,
|
||||
text=flair_template.text,
|
||||
css_class=flair_template.css_class,
|
||||
text_editable=flair_template.text_editable,
|
||||
sample=FlairTemplateSample(flair_template),
|
||||
position=getattr(c.site, 'flair_position', 'right'))
|
||||
|
||||
def render(self, *a, **kw):
|
||||
res = Templated.render(self, *a, **kw)
|
||||
if not g.template_debug:
|
||||
res = spaceCompress(res)
|
||||
return res
|
||||
|
||||
class FlairTemplateSample(Templated):
|
||||
"""Like a read-only version of FlairTemplateEditor."""
|
||||
def __init__(self, flair_template):
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site,
|
||||
force_show_flair=True,
|
||||
flair_template=flair_template)
|
||||
Templated.__init__(self, flair_template_id=flair_template._id,
|
||||
wrapped_user=wrapped_user)
|
||||
|
||||
class FlairSelector(CachedTemplate):
|
||||
"""Provide user with flair options according to subreddit settings."""
|
||||
def __init__(self):
|
||||
get_flair_attr = lambda a, default=None: getattr(
|
||||
c.user, 'flair_%s_%s' % (c.site._id, a), default)
|
||||
user_flair_enabled = get_flair_attr('enabled', default=True)
|
||||
text = get_flair_attr('text')
|
||||
css_class = get_flair_attr('css_class')
|
||||
sr_flair_enabled = getattr(c.site, 'flair_enabled', True)
|
||||
|
||||
ids = FlairTemplateBySubredditIndex.get_template_ids(c.site._id)
|
||||
# TODO(intortus): Maintain sorting.
|
||||
templates = FlairTemplate._byID(ids).values()
|
||||
for template in templates:
|
||||
if template.covers((text, css_class)):
|
||||
matching_template = template._id
|
||||
break
|
||||
else:
|
||||
matching_template = None
|
||||
|
||||
choices = [WrappedUser(c.user, subreddit=c.site, force_show_flair=True,
|
||||
flair_template=template)
|
||||
for template in templates]
|
||||
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site,
|
||||
force_show_flair=True)
|
||||
|
||||
Templated.__init__(self, user_flair_enabled=user_flair_enabled,
|
||||
text=text, css_class=css_class,
|
||||
sr_flair_enabled=sr_flair_enabled,
|
||||
choices=choices, matching_template=matching_template,
|
||||
wrapped_user=wrapped_user)
|
||||
|
||||
|
||||
class FriendList(UserList):
|
||||
"""Friend list on /pref/friends"""
|
||||
type = 'friend'
|
||||
|
||||
@@ -87,6 +87,10 @@ class FlairTemplate(tdb_cassandra.Thing):
|
||||
|
||||
@classmethod
|
||||
def _new(cls, text='', css_class='', text_editable=False):
|
||||
if text is None:
|
||||
text = ''
|
||||
if css_class is None:
|
||||
css_class = ''
|
||||
ft = cls(text=text, css_class=css_class, text_editable=text_editable)
|
||||
ft._commit()
|
||||
return ft
|
||||
@@ -97,6 +101,29 @@ class FlairTemplate(tdb_cassandra.Thing):
|
||||
self._id = str(uuid.uuid1())
|
||||
return tdb_cassandra.Thing._commit(self, *a, **kw)
|
||||
|
||||
def covers(self, other_template):
|
||||
"""Returns true if other_template is a subset of this one.
|
||||
|
||||
The value for other_template may be another FlairTemplate, or a tuple
|
||||
of (text, css_class). The latter case is treated like a FlairTemplate
|
||||
that doesn't permit editable text.
|
||||
|
||||
For example, if self permits editable text, then this method will return
|
||||
True as long as just the css_classes match. On the other hand, if self
|
||||
doesn't permit editable text but other_template does, this method will
|
||||
return False.
|
||||
"""
|
||||
if isinstance(other_template, FlairTemplate):
|
||||
text_editable = other_template.text_editable
|
||||
text, css_class = other_template.text, other_template.css_class
|
||||
else:
|
||||
text_editable = False
|
||||
text, css_class = other_template
|
||||
|
||||
if self.css_class != css_class:
|
||||
return False
|
||||
return self.text_editable or (not text_editable and self.text == text)
|
||||
|
||||
|
||||
class FlairTemplateBySubredditIndex(tdb_cassandra.Thing):
|
||||
"""A list of FlairTemplate IDs for a subreddit.
|
||||
|
||||
@@ -671,6 +671,9 @@ ul.flat-vert {text-align: left;}
|
||||
margin-right: 4ex;
|
||||
}
|
||||
|
||||
.flairsample-left { text-align: right !important; }
|
||||
.flairsample-right { text-align: left !important; }
|
||||
|
||||
.flairrow .tagline { text-align: left; width:36ex; }
|
||||
.flairlist .header { text-align: center; }
|
||||
.flairlist .flaircell input[type="text"] { width: 28ex; }
|
||||
|
||||
@@ -14,18 +14,26 @@ $(function() {
|
||||
showSaveButton(this);
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
function onSubmit(action) {
|
||||
$(this).removeClass("edited");
|
||||
return post_form(this, "flair");
|
||||
return post_form(this, action);
|
||||
}
|
||||
|
||||
/* Attach event handlers to the various flair forms that may be on page. */
|
||||
$(".flairrow form").submit(onSubmit);
|
||||
$(".flaircell input").focus(onFocus);
|
||||
$(".flaircell input").keyup(onEdit);
|
||||
function makeOnSubmit(action) {
|
||||
return function() { return onSubmit.call(this, action); };
|
||||
}
|
||||
|
||||
// Attach event handlers to the various flair forms that may be on page.
|
||||
$(".flairlist").delegate(".flairtemplate form", "submit",
|
||||
makeOnSubmit('flairtemplate'));
|
||||
$(".flairlist").delegate("form.flair-entry", "submit",
|
||||
makeOnSubmit('flair'));
|
||||
$(".flairlist").delegate(".flaircell input", "focus", onFocus);
|
||||
$(".flairlist").delegate(".flaircell input", "keyup", onEdit);
|
||||
|
||||
// Event handlers for sidebar flair prefs.
|
||||
$(".flairtoggle").submit(function() {
|
||||
return post_form(this, 'setflairenabled');
|
||||
});
|
||||
return post_form(this, 'setflairenabled');
|
||||
});
|
||||
$(".flairtoggle input").change(function() { $(this).parent().submit(); });
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
${unsafe(thing.tabs.render())}
|
||||
|
||||
${unsafe(thing.flair_list.render())}
|
||||
</div>
|
||||
|
||||
44
r2/r2/templates/flairselector.html
Normal file
44
r2/r2/templates/flairselector.html
Normal file
@@ -0,0 +1,44 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
## with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is Reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
%if thing.sr_flair_enabled:
|
||||
<form class="toggle flairtoggle">
|
||||
<input id="flair_enabled" type="checkbox" name="flair_enabled"
|
||||
%if thing.user_flair_enabled:
|
||||
checked="checked"
|
||||
%endif
|
||||
>
|
||||
${_("Show my flair on this reddit. It looks like:")}
|
||||
<div class="flairselector">
|
||||
<input type="hidden" name="flair_template_id">
|
||||
<span class="dropdown selected">${thing.wrapped_user}</span>
|
||||
<ul class="drop-choices">
|
||||
%for choice in thing.choices:
|
||||
<li class="flairsample-${thing.position}"
|
||||
id="${choice.flair_template_id}">
|
||||
${unsafe(choice.render())}
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
%endif
|
||||
49
r2/r2/templates/flairtemplateeditor.html
Normal file
49
r2/r2/templates/flairtemplateeditor.html
Normal file
@@ -0,0 +1,49 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
## with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is Reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
<%namespace name="utils" file="utils.html"/>
|
||||
|
||||
<div class="flairtemplate flairrow">
|
||||
<form action="/post/flairtemplate"
|
||||
method="post" class="medium-text flair-entry">
|
||||
%if thing.id:
|
||||
<input type="hidden" name="id" value="${thing.id}" />
|
||||
%endif
|
||||
<span class="flaircell flairsample-${thing.position} tagline">
|
||||
%if thing.text or thing.css_class:
|
||||
${unsafe(thing.sample.render())}
|
||||
%endif
|
||||
</span>
|
||||
<span class="flaircell">
|
||||
<input type="text" size="32" maxlength="64" name="text"
|
||||
value="${thing.text}" />
|
||||
</span>
|
||||
<span class="flaircell">
|
||||
<input type="text" size="32" maxlength="1000" name="css_class"
|
||||
value="${thing.css_class}" />
|
||||
</span>
|
||||
<button type="submit">save</button>
|
||||
<span class="status"></span>
|
||||
${utils.error_field('BAD_CSS_NAME', 'css_class')}
|
||||
${utils.error_field('TOO_MUCH_FLAIR_CSS', 'css_class')}
|
||||
</form>
|
||||
</div>
|
||||
40
r2/r2/templates/flairtemplatelist.html
Normal file
40
r2/r2/templates/flairtemplatelist.html
Normal file
@@ -0,0 +1,40 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
## with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is Reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
<%!
|
||||
from r2.models import FlairTemplate
|
||||
from r2.lib.pages.pages import FlairTemplateEditor
|
||||
|
||||
empty_template = FlairTemplate()
|
||||
empty_template._committed = True # to disable unnecessary warning
|
||||
%>
|
||||
|
||||
<div class="usertable">
|
||||
<div class="flairtemplatelist flairlist pretty-form">
|
||||
%for flair_template in thing.templates:
|
||||
${unsafe(flair_template.render())}
|
||||
%endfor
|
||||
<div id="empty-flair-template">
|
||||
${unsafe(FlairTemplateEditor(empty_template).render())}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
23
r2/r2/templates/flairtemplatesample.html
Normal file
23
r2/r2/templates/flairtemplatesample.html
Normal file
@@ -0,0 +1,23 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
## with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is Reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
${unsafe(thing.wrapped_user.render())}
|
||||
@@ -36,22 +36,6 @@
|
||||
hidden_data = dict(id = thing.sr._fullname))}
|
||||
</%def>
|
||||
|
||||
<%def name="flair_control(flair_user)">
|
||||
%if flair_user:
|
||||
<form class="toggle flairtoggle">
|
||||
<input id="flair_enabled" type="checkbox" name="flair_enabled"
|
||||
%if flair_user.flair_enabled:
|
||||
checked="checked"
|
||||
%endif
|
||||
>
|
||||
${_("Show my flair on this reddit. It looks like:")}
|
||||
<div class="tagline">
|
||||
${flair_user}
|
||||
</div>
|
||||
</form>
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<div class="titlebox">
|
||||
<h1 class="hover redditname">
|
||||
${plain_link(thing.sr.name, thing.sr.path, _sr_path=False, _class="hover")}
|
||||
@@ -75,7 +59,9 @@
|
||||
_("you are no longer an approved submitter"))}
|
||||
%endif
|
||||
|
||||
${flair_control(thing.flair_user)}
|
||||
%if thing.flair_selector:
|
||||
${unsafe(thing.flair_selector.render())}
|
||||
%endif
|
||||
|
||||
%if thing.sr.description:
|
||||
${thing.sr.usertext}
|
||||
|
||||
Reference in New Issue
Block a user