Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Single Theme view for sites. Bug 587616.
Browse files Browse the repository at this point in the history
  • Loading branch information
Fred Wenzel committed Aug 23, 2010
1 parent cc94f1b commit a9b3cf9
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 9 deletions.
2 changes: 1 addition & 1 deletion apps/dashboard/helpers.py
Expand Up @@ -24,7 +24,7 @@ def locales_block(context, locales, total, defaults=None):

@register.inclusion_tag('dashboard/message_list.html')
@jinja2.contextfunction
def message_list(context, opinions, defaults=None):
def message_list(context, opinions, defaults=None, show_notfound=True):
"""A list of messages."""
return new_context(**locals())

Expand Down
2 changes: 2 additions & 0 deletions apps/dashboard/templates/dashboard/message_list.html
Expand Up @@ -35,7 +35,9 @@
{% endfor %}
</ul>
{% else %}
{% if show_notfound %}
<ul class="messages">
<li id="message-warning">{{ _('No search results found.') }}</li>
</ul>
{% endif %}
{% endif %}
47 changes: 47 additions & 0 deletions apps/input/utils.py
Expand Up @@ -39,3 +39,50 @@ def manual_order(qs, pks, pk_name='id'):


crc32 = lambda x: zlib.crc32(x) & 0xffffffff


def cached_property(*args, **kw):
# Handles invocation as a direct decorator or
# with intermediate keyword arguments.
if args: # @cached_property
return CachedProperty(args[0])
else: # @cached_property(name=..., writable=...)
return lambda f: CachedProperty(f, **kw)


class CachedProperty(object):
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
and than that calculated result is used the next time you access
the value::
class Foo(object):
@cached_property
def foo(self):
# calculate something important here
return 42
Lifted from werkzeug.
"""

def __init__(self, func, name=None, doc=None, writable=False):
self.func = func
self.writable = writable
self.__name__ = name or func.__name__
self.__doc__ = doc or func.__doc__

def __get__(self, obj, type=None):
if obj is None:
return self
_missing = object()
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value

def __set__(self, obj, value):
if not self.writable:
raise TypeError('read only attribute')
obj.__dict__[self.__name__] = value
24 changes: 23 additions & 1 deletion apps/website_issues/models.py
@@ -1,8 +1,12 @@
import urlparse

from django.db import models

import caching.base

from feedback.models import ModelBase
from input.urlresolvers import reverse
from input.utils import cached_property


class Comment(ModelBase):
Expand All @@ -29,11 +33,14 @@ class Cluster(ModelBase):
class Meta:
ordering = ['-size']

@property
@cached_property
def secondary_comments(self):
qs = self.comments.exclude(pk=self.primary_comment_id)
return qs

def get_absolute_url(self):
return reverse('site_theme', args=[self.pk])


class QuerySetManager(caching.base.CachingManager):
"""Manager that allows to use a subclass of QuerySet."""
Expand Down Expand Up @@ -98,3 +105,18 @@ def _set_clusters(self, clusters):

class Meta:
ordering = ['-size', 'url']

@cached_property
def parsed_url(self):
return urlparse.urlparse(self.url)

@cached_property
def protocol(self):
return self.parsed_url.scheme

@cached_property
def domain(self):
return self.parsed_url.netloc

def get_absolute_url(self):
return reverse('single_site', args=[self.protocol, self.domain])
4 changes: 2 additions & 2 deletions apps/website_issues/templates/website_issues/single_site.html
@@ -1,4 +1,4 @@
<h2>{{ site.url|without_protocol }}</h2>
<h2>{{ site.domain }}</h2>

<a class="exit" href="{{ url('website_issues') }}">{{ _('Back to all sites &raquo;')|safe }}</a>

Expand All @@ -11,7 +11,7 @@ <h2>{{ site.url|without_protocol }}</h2>
<p class="type issue" title="_('Issue')"><span>_('Issue')</span></p>
{% endif %}

<p class="primary"><a href="#">
<p class="primary"><a href="{{ cluster.get_absolute_url() }}">
<span>{{ cluster.primary_description|as_unicode }}</span>
{% if cluster.size > 1 %}
<span class="more">{{ _('{count} similar messages')|f(count=(cluster.size-1)|numberfmt) }}</span>
Expand Down
Expand Up @@ -32,7 +32,7 @@ <h3>{{ _('No results found!') }}</h3>
<ul class="sites">
{% for site in sites %}
<li class="site">
<p class="name"><a href="{{ sites_url(form, url=site.url) }}">{{ site.url|without_protocol|as_unicode }}</a></p>
<p class="name"><a href="{{ sites_url(form, url=site.url) }}">{{ site.domain }}</a></p>
<ul class="meta">
<li><a href="{{ sites_url(form, url=site.url)}}">{{ _('{count} messages')|f(count=site.size|numberfmt) }}</a></li>
{% if site.positive is none %}
Expand Down
50 changes: 50 additions & 0 deletions apps/website_issues/templates/website_issues/theme.html
@@ -0,0 +1,50 @@
{% extends "base.html" %}

{% block page_title -%}
{{ site.domain }} :: {{ _('Sites') }}
{%- endblock %}
{% block body_id %}website_issues{% endblock %}
{% block header_links %}{{ header_links('website_issues') }}{% endblock %}

{% block content %}
<div class="col left">
{{ big_count_block(opinion_count) }}
</div><!--
--><div class="col middle wide">
<div id="messages" class="block">
<h2>{{ _('Theme') }}</h2>

<a class="exit" href="{{ site.get_absolute_url() }}">
{{ _('Back to {domain} &raquo;')|f(domain=site.domain)|safe }}</a>

<div id="theme-callout">
{{ cluster.primary_description }}
</div>

{{ message_list(opinions, show_notfound=False) }}

{# Pagination #}
{% if page and page.has_other_pages() %}
<div class="pager">
{% with link_txt = _('&laquo; Previous Page')|safe %}
{% if page.has_previous() %}
<a class="next" href="?page={{ page.previous_page_number() }}">{{ link_txt }}</a>
{% else %}
<span class="next inactive">{{ link_txt }}</span>
{% endif %}
{% endwith %}

{% with link_txt = _('Next Page &raquo;')|safe %}
{% if page.has_next() %}
<a class="prev" href="?page={{ page.next_page_number() }}">{{ link_txt }}</a>
{% else %}
<span class="prev inactive">{{ link_txt }}</span>
{% endif %}
{% endwith %}
</div>
{% endif %}

</div>
</div>
{% endblock %}
Expand Up @@ -7,7 +7,7 @@
{% endmacro %}

{% block page_title -%}
{% if site %}{{ site.url|without_protocol }} :: {% endif %}{{ _('Sites') }}
{% if site %}{{ site.domain }} :: {% endif %}{{ _('Sites') }}
{%- endblock %}
{% block body_id %}website_issues{% endblock %}
{% block header_links %}{{ header_links('website_issues') }}{% endblock %}
Expand Down Expand Up @@ -73,8 +73,7 @@ <h3>Min Related</h3>
<h3>{{ _('Uncommon Sites') }}</h3>
<ul>
{% for site in one_offs %}
<li><a href="{{ sites_url(form, site=site.id) }}">
{{ site.url|without_protocol|as_unicode }}</a></li>
<li><a href="{{ sites_url(form, site=site.id) }}">{{ site.domain }}</a></li>
{% endfor %}
<li class="more">{{ button('show_one_offs', True, label=_('More sites...')) }}</li>
</ul>
Expand Down
3 changes: 2 additions & 1 deletion apps/website_issues/urls.py
Expand Up @@ -3,5 +3,6 @@
urlpatterns = patterns('website_issues.views',
url(r'^sites/?$', 'website_issues', name='website_issues'),
url(r'^site/(?P<protocol>\w+)/(?P<url_>.+)$', 'single_site',
name='single_site')
name='single_site'),
url(r'^sites/theme/(?P<theme_id>\d+)$', 'site_theme', name='site_theme')
)
31 changes: 31 additions & 0 deletions apps/website_issues/views.py
@@ -1,11 +1,13 @@
from django.conf import settings
from django import http
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.shortcuts import get_object_or_404

import jingo

from input.decorators import cache_page
from feedback import LATEST_BETAS, FIREFOX
from feedback.models import Opinion

from .forms import WebsiteIssuesSearchForm
from .models import Comment, Cluster, SiteSummary
Expand Down Expand Up @@ -99,3 +101,32 @@ def single_site(request, protocol, url_):
"page": page,
"site": site,}
return jingo.render(request, 'website_issues/website_issues.html', data)


@cache_page(use_get=True)
def site_theme(request, theme_id):
"""Display all comments in a per-site cluster."""
cluster = get_object_or_404(Cluster, pk=theme_id)
comments = cluster.secondary_comments

# Paginate comments
try:
page_no = int(request.GET.get('page', '1'))
except ValueError:
page_no = 1
pager = Paginator(comments, settings.SEARCH_PERPAGE)
try:
page = pager.page(page_no)
except (EmptyPage, InvalidPage):
page = pager.page(pager.num_pages)

# Fetch full opinion list for this page
opinions = Opinion.objects.filter(
pk__in=(c.opinion_id for c in page.object_list))

data = {"cluster": cluster,
"page": page,
"opinion_count": pager.count + 1,
"opinions": opinions,
"site": cluster.site_summary,}
return jingo.render(request, 'website_issues/theme.html', data)

0 comments on commit a9b3cf9

Please sign in to comment.