summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-08 01:48:56 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-08 01:48:56 -0400
commit72c356b993771063e080c7cb47b8fc23a964dc1a (patch)
tree6047f195f5112f54a2a61c6b563a9e7d68012a5a
parentb94fddfb8495960569f6a6c64196a11d76b95fd4 (diff)
downloadaskbot-72c356b993771063e080c7cb47b8fc23a964dc1a.tar.gz
askbot-72c356b993771063e080c7cb47b8fc23a964dc1a.tar.bz2
askbot-72c356b993771063e080c7cb47b8fc23a964dc1a.zip
kind of merged question views, still have debugging to to
-rwxr-xr-xdjango_authopenid/views.py18
-rwxr-xr-xforum/const.py30
-rwxr-xr-xforum/forms.py73
-rw-r--r--forum/middleware/view_log.py63
-rwxr-xr-xforum/models/question.py61
-rwxr-xr-xforum/models/tag.py4
-rw-r--r--forum/search/__init__.py0
-rw-r--r--forum/search/state_manager.py127
-rwxr-xr-xforum/skins/default/media/style/style.css20
-rw-r--r--forum/skins/default/templates/header.html2
-rw-r--r--forum/skins/default/templates/questions.html126
-rw-r--r--forum/views/readers.py202
-rw-r--r--locale/en/LC_MESSAGES/django.mobin27146 -> 27175 bytes
-rw-r--r--locale/en/LC_MESSAGES/django.po2
-rw-r--r--settings.py1
15 files changed, 537 insertions, 192 deletions
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index 3c794a0d..e2d8b67c 100755
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -71,6 +71,7 @@ from forum.utils.forms import get_next_url
EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
+#todo: decouple from forum
def login(request,user):
from django.contrib.auth import login as _login
from forum.models import user_logged_in #custom signal
@@ -80,14 +81,27 @@ def login(request,user):
#1) get old session key
session_key = request.session.session_key
- #2) login and get new session key
+ #2) get old search state
+ search_state = None
+ if 'search_state' in request.session:
+ search_state = request.session['search_state']
+
+ #3) login and get new session key
_login(request,user)
- #3) send signal with old session key as argument
+ #4) transfer search_state to new session if found
+ if search_state:
+ search_session.set_logged_in()
+ request.session['search_state'] = search_state
+ #5) send signal with old session key as argument
logging.debug('logged in user %s with session key %s' % (user.username, session_key))
user_logged_in.send(user=user,session_key=session_key,sender=None)
+#todo: uncouple this from forum
def logout(request):
from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
+ if 'search_state' in request.session:
+ request.session['search_state'].set_logged_out()
+ request.session.modified = True
_logout(request)
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
EXTERNAL_LOGIN_APP.api.logout(request)
diff --git a/forum/const.py b/forum/const.py
index 7b78032a..6f6086f0 100755
--- a/forum/const.py
+++ b/forum/const.py
@@ -32,19 +32,34 @@ TYPE_REPUTATION = (
(-8, 'lose_by_upvote_canceled'),
)
+#do not translate these!!!
POST_SORT_METHODS = (
- _('newest'), _('oldest'),_('active'),
- _('inactive'),_('hot'),_('cold'),
- _('popular'),_('unpopular'),_('relevance')
+ ('latest', _('newest')),
+ ('oldest', _('oldest')),
+ ('active', _('active')),
+ ('inactive', _('inactive')),
+ ('hottest', _('hottest')),
+ ('coldest', _('coldest')),
+ ('mostvoted', _('most voted')),
+ ('leastvoted', _('least voted')),
+ ('relevant', _('relevance')),
)
#todo: add assertion here that all sort methods are unique
#because they are keys to the hash used in implementations of Q.run_advanced_search
-DEFAULT_POST_SORT_METHOD = _('newest')
-POST_SCOPES = (_('all'),_('unanswered'),_('favorite'))
-DEFAULT_POST_SCOPE = _('all')
+DEFAULT_POST_SORT_METHOD = 'latest'
+POST_SCOPE_LIST = (
+ ('all', _('all')),
+ ('unanswered', _('unanswered')),
+ ('favorite', _('favorite')),
+ )
+DEFAULT_POST_SCOPE = 'all'
+DEFAULT_QUESTIONS_PAGE_SIZE = 30
+PAGE_SIZES = (10,30,50)
-QUESTIONS_PAGE_SIZE = 30
+UNANSWERED_MEANING_LIST = ('NO_ANSWERS','NO_UPVOTED_ANSWERS','NO_ACCEPTED_ANSWERS')
+UNANSWERED_MEANING = 'NO_ANSWERS'
+assert(UNANSWERED_MEANING in UNANSWERED_MEANING_LIST)
#todo:
#this probably needs to be language-specific
@@ -53,6 +68,7 @@ QUESTIONS_PAGE_SIZE = 30
#correct regexes - plus this must be an anchored regex
#to do full string match
TAG_REGEX = r'^[a-z0-9\+\.\-]+$'
+TAG_SPLIT_REGEX = r'[ ,]+'
MAX_TAG_LENGTH = 20 #default 20 chars
MAX_TAGS_PER_POST = 5 #no more than five tags
diff --git a/forum/forms.py b/forum/forms.py
index dbdc4e9f..c32ce6c0 100755
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -61,7 +61,7 @@ class TagNamesField(forms.CharField):
if len(data) < 1:
raise forms.ValidationError(_('tags are required'))
- split_re = re.compile(r'[ ,]+')
+ split_re = re.compile(const.TAG_SPLIT_REGEX)
tag_strings = split_re.split(data)
out_tag_list = []
tag_count = len(tag_strings)
@@ -79,7 +79,7 @@ class TagNamesField(forms.CharField):
msg = ungettext('each tag must be shorter than %(max_chars)d character',#odd but added for completeness
'each tag must be shorter than %(max_shars)d characters',
tag_length) % {'max_chars':tag_length}
- raise forms.ValidationError(msg)_
+ raise forms.ValidationError(msg)
#todo - this needs to come from settings
tagname_re = re.compile(const.TAG_REGEX, re.UNICODE)
@@ -127,6 +127,75 @@ class ModerateUserForm(forms.ModelForm):
model = User
fields = ('is_approved',)
+class AdvancedSearchForm(forms.Form):
+ #nothing must be required in this form
+ #it is used by the main questions view
+ scope = forms.ChoiceField(choices=const.POST_SCOPE_LIST, required=False)
+ sort = forms.ChoiceField(choices=const.POST_SORT_METHODS, required=False)
+ query = forms.CharField(max_length=256, required=False)
+ reset_tags = forms.BooleanField(required=False)
+ reset_author = forms.BooleanField(required=False)
+ reset_query = forms.BooleanField(required=False)
+ tags = forms.CharField(max_length=256,required=False)
+ author = forms.IntegerField(required=False)
+ page_size = forms.ChoiceField(choices=const.PAGE_SIZES, required=False)
+ page = forms.IntegerField(required=False)
+
+ def clean_tags(self):
+ if 'tags' in self.cleaned_data:
+ tags_input = self.cleaned_data['tags'].strip()
+ split_re = re.compile(TAG_SPLIT_REGEX)
+ tag_strings = split_re.split(tags_input)
+ tagname_re = re.compile(const.TAG_REGEX, re.UNICODE)
+ out = set()
+ for s in tag_strings:
+ if tagname_re.search(s):
+ out.add(s)
+ if len(out) > 0:
+ self.cleaned_data['tags'] = out
+ else:
+ self.cleaned_data['tags'] = None
+ return self.cleaned_data['tags']
+
+ def clean_query(self):
+ if 'query' in self.cleaned_data:
+ q = self.cleaned_data['query'].strip()
+ if q == '':
+ q = None
+ self.cleaned_data['query'] = q
+ return self.cleaned_data['query']
+
+ def clean_page_size(self):
+ if 'page_size' in self.cleaned_data:
+ if self.cleaned_data['page_size'] == '':
+ self.cleaned_data['page_size'] = None
+ return self.cleaned_data['page_size']
+
+ def clean(self):
+ #todo rewrite
+ print self.cleaned_data
+ if self.cleaned_data['scope'] == '':
+ del self.cleaned_data['scope']
+ if self.cleaned_data['tags'] is None:
+ del self.cleaned_data['tags']
+ if self.cleaned_data['sort'] == '':
+ del self.cleaned_data['sort']
+ if self.cleaned_data['query'] == None:
+ del self.cleaned_data['query']
+ if self.cleaned_data['reset_tags'] == False:
+ del self.cleaned_data['reset_tags']
+ if self.cleaned_data['reset_author'] == False:
+ del self.cleaned_data['reset_author']
+ if self.cleaned_data['reset_query'] == False:
+ del self.cleaned_data['reset_query']
+ if self.cleaned_data['author'] is None:
+ del self.cleaned_data['author']
+ if self.cleaned_data['page'] is None:
+ del self.cleaned_data['page']
+ if self.cleaned_data['page_size'] is None:
+ del self.cleaned_data['page_size']
+ return self.cleaned_data
+
class NotARobotForm(forms.Form):
recaptcha = ReCaptchaField()
diff --git a/forum/middleware/view_log.py b/forum/middleware/view_log.py
new file mode 100644
index 00000000..6f59568a
--- /dev/null
+++ b/forum/middleware/view_log.py
@@ -0,0 +1,63 @@
+import logging
+from django.conf import settings
+from forum.views.readers import questions as questions_view
+from forum.views.commands import vote as vote_view
+from django.views.static import serve as django_serve_view
+
+class ViewLog(object):
+ """must be modified only in this middlware
+ however, can be read anywhere else
+ """
+ def __init__(self):
+ self.views = []
+ self.depth = 3 #todo maybe move this to const.py
+ def set_current(self, view_name):
+ thi
+
+ def get_previous(self, num):
+ if num > self.depth - 1:
+ raise Exception("view log depth exceeded")
+ elif num < 0:
+ raise Exception("num must be positive");
+ elif num <= len(self.views) - 1:
+ return self.views[num]
+ else:
+ return None
+
+ def set_current(self, view_name):
+ self.views.insert(0, view_name)
+ if len(self.views) > self.depth:
+ self.views.pop()
+
+ def __str__(self):
+ return str(self.views) + ' depth=%d' % self.depth
+
+class ViewLogMiddleware(object):
+ def process_view(self, request, view_func, view_args, view_kwargs):
+ if view_func == questions_view:
+ view_str = 'questions'
+ elif view_func in (django_serve_view, vote_view):
+ return
+ elif settings.DEBUG == True:
+ #todo: dependency!
+ from debug_toolbar.views import debug_media_view
+ if view_func == debug_media_view:
+ return
+ else:
+ view_str = view_func.__name__
+ else:
+ view_str = view_func.__name__
+
+ if request.user.is_authenticated():
+ user_name = request.user.username
+ else:
+ user_name = request.META['REMOTE_ADDR']
+ logging.debug('user %s, view %s' % (request.user.username, view_str))
+
+ if 'view_log' in request.session:
+ view_log = request.session['view_log']
+ else:
+ view_log = ViewLog()
+
+ view_log.set_current(view_str)
+ request.session['view_log'] = view_log
diff --git a/forum/models/question.py b/forum/models/question.py
index 71da0396..98b50490 100755
--- a/forum/models/question.py
+++ b/forum/models/question.py
@@ -1,24 +1,31 @@
-from base import *
+from base import * #todo maybe remove *
from tag import Tag
+#todo: make uniform import for consts
from forum.const import CONST
+from forum import const
from forum.utils.html import sanitize_html
from markdown2 import Markdown
from django.utils.html import strip_tags
import datetime
+from django.conf import settings
+from django.utils.datastructures import SortedDict
+from forum.models.tag import MarkedTag
+
markdowner = Markdown(html4tags=True)
from forum.utils.lists import LazyList
+#todo: too bad keys are duplicated see const sort methods
QUESTION_ORDER_BY_MAP = {
- _('newest'): '-added_at',
- _('oldest'): 'added_at',
- _('active'): '-last_activity_at',
- _('inactive'): 'last_activity_at',
- _('hot'): '-answer_count',
- _('cold'): 'answer_count'
- _('popular'): '-score',
- _('unpopular'): 'score',
- _('relevance'):None #this is a special case
+ 'latest': '-added_at',
+ 'oldest': 'added_at',
+ 'active': '-last_activity_at',
+ 'inactive': 'last_activity_at',
+ 'hottest': '-answer_count',
+ 'coldest': 'answer_count',
+ 'mostvoted': '-score',
+ 'leastvoted': 'score',
+ 'relevant': None #this is a special case
}
class QuestionManager(models.Manager):
@@ -54,7 +61,7 @@ class QuestionManager(models.Manager):
def run_advanced_search(
self,
request_user = None,
- scope_selector = scope_selector,#unanswered/all/favorite (for logged in)
+ scope_selector = const.DEFAULT_POST_SCOPE,#unanswered/all/favorite (for logged in)
search_query = None,
tag_selector = None,
author_selector = None,#???question or answer author or just contributor
@@ -65,16 +72,32 @@ class QuestionManager(models.Manager):
a relvant filter will be silently dropped
"""
+ qs = self.filter(deleted=False)#todo - add a possibility to see deleted questions
+
#return metadata
meta_data = {}
if tag_selector:
qs = qs.filter(tags__name__in = tag_selector)
+ if search_query:
+ qs = qs.filter(deleted=False).extra(
+ where=['title like %s'],
+ params=['%' + search_query + '%']
+ )
+
if scope_selector:
+ if scope_selector == 'unanswered':
+ if const.UNANSWERED_MEANING == 'NO_ANSWERS':
+ qs = qs.filter(answer_count=0)#todo: expand for different meanings of this
+ elif const.UNANSWERED_MEANING == 'NO_ACCEPTED_ANSWERS':
+ qs = qs.filter(answer_accepted=False)
+ elif const.UNANSWERED_MEANING == 'NO_UPVOTED_ANSWERS':
+ raise NotImplementedError()
+ else:
+ raise Exception('UNANSWERED_MEANING setting is wrong')
+ elif scope_selector == 'favorite':
+ qs = qs.filter(favorited_by = request_user)
- if unanswered:
- qs = qs.exclude(answer_accepted=True)
-
#user contributed questions & answers
if author_selector:
try:
@@ -126,9 +149,13 @@ class QuestionManager(models.Manager):
meta_data['interesting_tag_names'] = pt.filter(reason='good').values_list('tag__name', flat=True)
meta_data['ignored_tag_names'] = pt.filter(reason='bad').values_list('tag__name', flat=True)
- #todo: fix orderby here
- qs = qs.select_related(depth=1).order_by(orderby)
- return qs, meta_data
+ qs = qs.select_related(depth=1)
+ #todo: fix orderby here
+ orderby = QUESTION_ORDER_BY_MAP[sort_method]
+ if orderby:
+ #relevance will be ignored here
+ qs = qs.order_by(orderby)
+ return qs, meta_data
def update_tags(self, question, tagnames, user):
"""
diff --git a/forum/models/tag.py b/forum/models/tag.py
index 8d26d6f4..e13baf9b 100755
--- a/forum/models/tag.py
+++ b/forum/models/tag.py
@@ -49,6 +49,8 @@ class TagManager(models.Manager):
def get_tags_by_questions(self, questions):
question_ids = []
+ if len(questions) == 0:
+ return []
for question in questions:
question_ids.append(question.id)
@@ -82,4 +84,4 @@ class MarkedTag(models.Model):
reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS)
class Meta:
- app_label = 'forum' \ No newline at end of file
+ app_label = 'forum'
diff --git a/forum/search/__init__.py b/forum/search/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/forum/search/__init__.py
diff --git a/forum/search/state_manager.py b/forum/search/state_manager.py
new file mode 100644
index 00000000..b36c5e53
--- /dev/null
+++ b/forum/search/state_manager.py
@@ -0,0 +1,127 @@
+#search state manager object
+#that lives in the session and takes care of the state
+#persistece during the search session
+from forum import const
+import logging
+
+class SearchState(object):
+ def __init__(self):
+ self.scope= const.DEFAULT_POST_SCOPE
+ self.query = None
+ self.tags = None
+ self.author = None
+ self.sort = const.DEFAULT_POST_SORT_METHOD
+ self.page_size = const.DEFAULT_QUESTIONS_PAGE_SIZE
+ self.page = 1
+ self.logged_in = False
+ logging.debug('new search state initialized')
+ print 'new search state'
+
+ def __str__(self):
+ out = 'scope=%s\n' % self.scope
+ out += 'query=%s\n' % self.query
+ if self.tags:
+ out += 'tags=%s\n' % ','.join(self.tags)
+ out += 'author=%s\n' % self.author
+ out += 'sort=%s\n' % self.sort
+ out += 'page_size=%d\n' % self.page_size
+ out += 'page=%d\n' % self.page
+ out += 'logged_in=%s\n' % str(self.logged_in)
+ return out
+
+ def set_logged_out(self):
+ if self.scope == 'favorite':
+ self.scope = None
+ self.logged_in = False
+
+ def set_logged_in(self):
+ self.logged_in = True
+
+ def reset(self):
+ #re-initialize, but keep login state
+ is_logged_in = self.logged_in
+ self.__init__()
+ self.logged_in = is_logged_in
+
+ def update_value(self, key, store):
+ if key in store:
+ old_value = getattr(self, key)
+ new_value = store[key]
+ if new_value != old_value:
+ setattr(self, key, new_value)
+ self.reset_page()
+
+ def update_from_user_input(self,input):
+ #todo: this function will probably not
+ #fit the case of multiple parameters entered at the same tiem
+ print ','.join(input.keys())
+ if 'page' in input:
+ print input['page']
+ self.page = input['page']
+ #special case - on page flip no other input is accepted
+ return
+
+ if 'page_size' in input:
+ self.update_value('page_size',input)
+ self.reset_page()#todo may be smarter here - start with ~same q
+ #same as with page - return right away
+ return
+
+ if 'scope' in input:
+ if input['scope'] == 'favorite' and self.logged_in == False:
+ self.reset_scope()
+ else:
+ self.update_value('scope',input)
+
+ if 'tags' in input:
+ if self.tags:
+ old_tags = self.tags.copy()
+ self.tags.union(input['tags'])
+ if self.tags != old_tags:
+ self.reset_page()
+ else:
+ self.tags = input['tags']
+
+ #all resets just return
+ if 'reset_tags' in input:
+ if self.tags:
+ self.tags = None
+ self.reset_page()
+ return
+
+ #todo: handle case of deleting tags one-by-one
+ if 'reset_author' in input:
+ if self.author:
+ self.author = None
+ self.reset_page()
+ return
+
+ if 'reset_query' in input:
+ if self.query:
+ self.query = None
+ self.reset_page()
+ if self.sort == 'relevant':
+ self.reset_sort()
+ return
+
+ self.update_value('author',input)
+
+ if 'query' in input:
+ self.update_value('query',input)
+ self.sort = 'relevant'
+
+
+ if 'sort' in input:
+ if input['sort'] == 'relevant' and self.query is None:
+ self.reset_sort()
+ else:
+ self.update_value('sort',input)
+
+ def reset_page(self):
+ self.page = 1
+
+ def reset_sort(self):
+ self.sort = const.DEFAULT_POST_SORT_METHOD
+
+ def reset_scope(self):
+ self.scope = const.DEFAULT_POST_SCOPE
diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css
index 93c979ce..8315839d 100755
--- a/forum/skins/default/media/style/style.css
+++ b/forum/skins/default/media/style/style.css
@@ -866,7 +866,16 @@ a:hover.medal {
height: 20px;
}
-.tabsA a.on, .tabsA a:hover, .tabsB a.on, .tabsB a:hover {
+.tabsC {
+ background-color: #FFF;
+ float: left;
+ position: relative;
+ display: block;
+ font-weight: bold;
+ height: 20px;
+}
+
+.tabsA a.on, .tabsA a:hover, .tabsB a.on, .tabsB a:hover , .tabsC a.on, tabsC a:hover {
background: #fff;
color: #a40000;
border-top: 1px solid #babdb6;
@@ -879,7 +888,7 @@ a:hover.medal {
padding: 0px 11px 0px 11px;
}
-.tabsA a {
+.tabsA a, .tabsC a{
background: #f9f7eb;
border-top: 1px solid #eeeeec;
border-left: 1px solid #eeeeec;
@@ -895,6 +904,12 @@ a:hover.medal {
text-decoration: none;
}
+.tabsA .label, .tabsC .label {
+ float: left;
+ font-weight: bold;
+ margin: 8px 4px 0 4px;
+}
+
.tabsB a {
background: #eee;
border: 1px solid #eee;
@@ -920,7 +935,6 @@ a:hover.medal {
}
.headQuestions {
- float: left;
height: 23px;
line-height: 23px;
margin: 5px 0 0 5px;
diff --git a/forum/skins/default/templates/header.html b/forum/skins/default/templates/header.html
index 099bfb85..aaf19874 100644
--- a/forum/skins/default/templates/header.html
+++ b/forum/skins/default/templates/header.html
@@ -31,7 +31,9 @@
<a id="nav_books" href="{% url books %}">{% trans "books" %}</a>
{% endif %}
<a id="nav_badges" href="{% url badges %}">{% trans "badges" %}</a>
+ {% comment %}
<a id="nav_unanswered" href="{% url unanswered %}">{% trans "unanswered questions" %}</a>
+ {% endcomment %}
<div class="focus">
<a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a>
</div>
diff --git a/forum/skins/default/templates/questions.html b/forum/skins/default/templates/questions.html
index 366727d1..0a7c0c96 100644
--- a/forum/skins/default/templates/questions.html
+++ b/forum/skins/default/templates/questions.html
@@ -10,9 +10,11 @@
<script type="text/javascript">
var tags = {{ tags_autocomplete|safe }};
$().ready(function(){
- var tab_id = "{{ tab_id }}";
- $("#"+tab_id).attr('className',"on");
- var on_tab = {% if is_unanswered %}'#nav_unanswered'{% else %}'#nav_questions'{% endif %};
+ var sort_tab_id = "{{ sort }}";
+ $("#"+sort_tab_id).attr('className',"on");
+ var scope_tab_id = "{{ scope }}";
+ $("#"+scope_tab_id).attr('className',"on");
+ var on_tab = '#nav_questions';
$(on_tab).attr('className','on');
Hilite.exact = false;
Hilite.elementid = "listA";
@@ -24,6 +26,7 @@
{% endblock %}
{% block content %}
<div class="tabBar">
+ {% comment %}
<div class="headQuestions">
{% if searchtag %}
{% trans "Found by tags" %}
@@ -42,27 +45,116 @@
{% endif %}
{% endif %}
{% endif %}
+ </div><br/>
+ {% endcomment %}
+ <div class="tabsC">
+ <span class="label">{% trans "Pick:" %}</span>
+ <a id="all" class="off" href="?scope=all" title="{% trans "see all questions" %}">{% trans "all" %}</a>
+ <a id="unanswered" class="off" href="?scope=unanswered" title="{% trans "see unanswered questions" %}">{% trans "unanswered" %}</a>
+ {% if request.user.is_authenticated %}
+ <a id="favorite" class="off" href="?scope=favorite" title="{% trans "see your favorite questions" %}">{% trans "favorite" %}</a>
+ {% endif %}
</div>
<div class="tabsA">
- <a id="latest" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
- <a id="active" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
- <a id="hottest" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=hottest" class="off" title="{% trans "hottest questions" %}">{% trans "hottest" %}</a>
- <a id="mostvoted" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=mostvoted" class="off" title="{% trans "most voted questions" %}">{% trans "most voted" %}</a>
+ <span class="label">{% trans "Sort by:" %}</span>
+ {% if sort == "oldest" %}
+ <a id="oldest"
+ href="?sort=latest"
+ class="off"
+ title="{% trans "click to see the newest questions" %}">{% trans "oldest" %}</a>
+ {% else %}
+ {% if sort == "latest" %}
+ <a id="latest"
+ href="?sort=oldest"
+ class="off"
+ title="{% trans "click to see the oldest questions" %}">{% trans "newest" %}</a>
+ {% else %}
+ <a id="latest"
+ href="?sort=latest"
+ class="off"
+ title="{% trans "click to see the newest questions" %}">{% trans "newest" %}</a>
+ {% endif %}
+ {% endif %}
+
+ {% if sort == "inactive" %}
+ <a id="inactive"
+ href="?sort=active"
+ class="off"
+ title="{% trans "click to see the most recently updated questions" %}">{% trans "inactive" %}</a>
+ {% else %}
+ {% if sort == "active" %}
+ <a id="active"
+ href="?sort=inactive"
+ class="off"
+ title="{% trans "click to see the least recently updated questions" %}">{% trans "active" %}</a>
+ {% else %}
+ <a id="active"
+ href="?sort=active"
+ class="off"
+ title="{% trans "click to see the most recently updated questions" %}">{% trans "active" %}</a>
+ {% endif %}
+ {% endif %}
+
+ {% if sort == "coldest" %}
+ <a id="coldest"
+ href="?sort=hottest"
+ class="off"
+ title="{% trans "click to see hottest questions" %}">{% trans "coldest" %}</a>
+ {% else %}
+ {% if sort == "hottest" %}
+ <a id="hottest"
+ href="?sort=coldest"
+ class="off"
+ title="{% trans "click to see coldest questions" %}">{% trans "hottest" %}</a>
+ {% else %}
+ <a id="hottest"
+ href="?sort=hottest"
+ class="off"
+ title="{% trans "click to see hottest questions" %}">{% trans "hottest" %}</a>
+ {% endif %}
+ {% endif %}
+
+ {% if sort == "leastvoted" %}
+ <a id="leastvoted"
+ href="?sort=mostvoted"
+ class="off"
+ title="{% trans "click to see most voted questions" %}">{% trans "unpopular" %}</a>
+ {% else %}
+ {% if sort == "mostvoted" %}
+ <a id="mostvoted"
+ href="?sort=leastvoted"
+ class="off"
+ title="{% trans "click to see least voted questions" %}">{% trans "popular" %}</a>
+ {% else %}
+ <a id="mostvoted"
+ href="?sort=mostvoted"
+ class="off"
+ title="{% trans "click to see most voted questions" %}">{% trans "popular" %}</a>
+ {% endif %}
+ {% endif %}
</div>
</div>
<div id="listA">
{% include "question_list.html" %}
- {% if searchtitle %}
{% if questions_count == 0 %}
- <p class="evenMore" style="padding-top:30px;text-align:center;">
- {% trans "Did not find anything?" %}
+ <p class="evenMore" style="padding-top:30px;text-align:center;">
+ {% if scope == "unanswered" %}
+ {% trans "There are no unanswered questions in this selection" %}
+ {% endif %}
+ {% if scope == "favorite" %}
+ {% trans "No favorite questions here. " %}
+ {% trans "Please bookmark some questions with a star and find them here later." %}
+ {% endif %}
+ {% if scope == "all" %}
+ {% trans "Did not find anything? Please feel free to ask your question!" %}
+ {% endif %}
+ </p>
{% else %}
- <p class="evenMore" style="padding-left:9px">
- {% trans "Did not find what you were looking for?" %}
+ <p class="evenMore" style="padding-left:9px">
+ {% trans "Did not find what you were looking for?" %}
+ <a href="{% url ask %}">{% trans "Please, post your question!" %}</a>
+ </p>
{% endif %}
- <a href="{% url ask %}">{% trans "Please, post your question!" %}</a>
- </p>
- {% endif %}
</div>
{% endblock %}
@@ -84,9 +176,9 @@
{% endif %}
{% else %}
{% if is_unanswered %}
- {% blocktrans count questions as cnt with questions_count|intcomma as q_num %} have total {{q_num}} unanswered questions {% plural %} have total {{q_num}} unanswered questions {% endblocktrans %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %} have total {{q_num}} unanswered questions {% plural %} have total {{q_num}} unanswered questions {% endblocktrans %}
{% else %}
- {% blocktrans count questions as cnt with questions_count|intcomma as q_num %} have total {{q_num}} questions {% plural %} have total {{q_num}} questions {% endblocktrans %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %} have total {{q_num}} questions {% plural %} have total {{q_num}} questions {% endblocktrans %}
{% endif %}
{% endif %}
{% endif %}
diff --git a/forum/views/readers.py b/forum/views/readers.py
index 8c15ffe7..9e9662dd 100644
--- a/forum/views/readers.py
+++ b/forum/views/readers.py
@@ -13,7 +13,6 @@ from django.db.models import Q
from django.utils.translation import ugettext as _
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse
-from django.utils.datastructures import SortedDict
from django.views.decorators.cache import cache_page
from forum.utils.html import sanitize_html
@@ -27,6 +26,7 @@ from forum.const import *
from forum import const
from forum import auth
from forum.utils.forms import get_next_url
+from forum.search.state_manager import SearchState
# used in index page
#refactor - move these numbers somewhere?
@@ -69,25 +69,7 @@ def index(request):#generates front page - shows listing of questions sorted in
def unanswered(request):#generates listing of unanswered questions
return questions(request, unanswered=True)
-def _get_and_stick(key, storage, defaults, data=None):
- """storage is request session in this case
- if a new value is coming from data, then storage will be updated
- this function assumes that data is clean
- """
- if key in storage:
- value = storage[key]
- else:
- value = defaults[key]
-
- if data and key in data:
- new_value = data[key]
- if new_value != value:
- storage[key] = new_value
- return new_value
- else:
- return value
-
-def questions(request, tagname=None, unanswered=False):#a view generating listing of questions, used by 'unanswered' too
+def questions(request):#a view generating listing of questions, used by 'unanswered' too
"""
List of Questions, Tagged questions, and Unanswered questions.
"""
@@ -96,147 +78,84 @@ def questions(request, tagname=None, unanswered=False):#a view generating listin
if request.method == 'POST':
raise Http404
- #default values
- DV = {
- 'scope_selector': const.DEFAULT_POST_SCOPE,
- 'search_query': None,
- 'tag_selector': None,
- 'author_selector': None,
- 'question_sort_method': const.DEFAULT_POST_SORT_METHOD,
- 'page_size': const.QUESTIONS_PAGE_SIZE,
- 'page_number': 1,
- }
-
- form = AdvancedSearchForm(request)
+ search_state = request.session.get('search_state', SearchState())
+
+ view_log = request.session['view_log']
+ print view_log
+ if view_log.get_previous(1) != 'questions':
+ if view_log.get_previous(2) != 'questions':
+ print 'user stepped too far, resetting search state'
+ search_state.reset()
+
+ if request.user.is_authenticated():
+ search_state.set_logged_in()
+
+ print 'before: ', search_state
+
+ form = AdvancedSearchForm(request.GET)
if form.is_valid():
-
- d = form.cleaned_data
- s = request.session
-
- scope_selector = _get_and_stick('scope_selector', s, DV, d)
- search_query = _get_and_stick('search_query', s, DV, d)
- tag_selector = _get_and_stick('tag_selector', s, DV, d)
- author_selector = _get_and_stick('author_selector', s, DV, d)
- sort_method = _get_and_stick('question_sort_method', s, DV, d)
- page_size = _get_and_stick('page_size', s, DV, d)
- else:
- s = request.session
- scope_selector = _get_and_stick('scope_selector', s, DV)
- search_query = _get_and_stick('search_query', s, DV)
- tag_selector = _get_and_stick('tag_selector', s, DV)
- author_selector = _get_and_stick('author_selector', s, DV)
- sort_method = _get_and_stick('question_sort_method', s, DV)
- page_size = _get_and_stick('page_size', s, DV)
+ print 'form is valid'
+ search_state.update_from_user_input(form.cleaned_data)
+ request.session['search_state'] = search_state
+ request.session.modified = True
+
+ print 'after: ', search_state
+
+ print 'going into the search'
#have this call implemented for sphinx, mysql and pgsql
- (questions, meta_data) = Question.objects.run_advanced_search(
+ (qs, meta_data) = Question.objects.run_advanced_search(
request_user = request.user,
- scope_selector = scope_selector,#unanswered/all/favorite (for logged in)
- search_query = search_query,
- tag_selector = tags,
- author_selector = author_selector,#???question or answer author or just contributor
- sort_method = sort_method#
+ scope_selector = search_state.scope,#unanswered/all/favorite (for logged in)
+ search_query = search_state.query,
+ tag_selector = search_state.tags,
+ author_selector = search_state.author,
+ sort_method = search_state.sort
)
- #maybe have this inside?
- questions = questions.select_related(depth=1).order_by(orderby)
- page = Paginator(questions).get_page(page_number)
- related_tags = Tag.objects.get_tags_by_questions(questions)
+ print 'got out of the search'
- objects_list = Paginator(qs, pagesize)
- questions = objects_list.page(page)#todo: is this right?
+ logging.debug('search state is %s' % search_state)
+
+ objects_list = Paginator(qs, search_state.page_size)
+ questions = objects_list.page(search_state.page)
+
+ #todo maybe do this search on query the set instead
+ related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
tags_autocomplete = _get_tags_cache_json()
+
+ #todo!!!!
#contributors = #User.objects.get_related_to_questions
- #todo make above form compatible with the template data
- #take whatever is missing from meta_data returned above
+ print 'rendering template!!!'
+ print 'have %d' % objects_list.count
+
return render_to_response('questions.html', {
- "questions" : questions,
- "author_name" : author_name,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "tags_autocomplete" : tags_autocomplete,
- "searchtag" : tagname,
- "is_unanswered" : unanswered,
- "interesting_tag_names": interesting_tag_names,
- 'ignored_tag_names': ignored_tag_names,
- "context" : {
+ 'questions' : questions,
+ 'author_name' : meta_data.get('author_name',None),
+ 'tab_id' : search_state.sort,
+ 'questions_count' : objects_list.count,
+ 'tags' : related_tags,
+ 'tags_autocomplete' : tags_autocomplete,
+ 'searchtag' : search_state.tags,
+ 'is_unanswered' : False,#remove this from template
+ 'interesting_tag_names': meta_data.get('interesting_tag_names',None),
+ 'ignored_tag_names': meta_data.get('ignored_tag_names',None),
+ 'sort': search_state.sort,
+ 'scope': search_state.scope,
+ 'context' : {
'is_paginated' : True,
'pages': objects_list.num_pages,
- 'page': page,
+ 'page': search_state.page,
'has_previous': questions.has_previous(),
'has_next': questions.has_next(),
'previous': questions.previous_page_number(),
'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : pagesize
+ 'base_url' : request.path + '?sort=%s&' % search_state.sort,#todo in T sort=>sort_method
+ 'pagesize' : search_state.page_size,#todo in T pagesize -> page_size
}}, context_instance=RequestContext(request))
-def fulltext(request):
- if request.method == "GET":
- keywords = request.GET.get("q")
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- def question_search(keywords, orderby):
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
- # RISK - inner join queries
- return objects.select_related();
-
- from forum.modules import get_handler
-
- question_search = get_handler('question_search', question_search)
-
- objects = question_search(keywords, orderby)
-
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- related_tags = []
- for question in questions.object_list:
- tags = list(question.tags.all())
- for tag in tags:
- if tag not in related_tags:
- related_tags.append(tag)
-
- #if is_search is true in the context, prepend this string to soting tabs urls
- search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : None,
- "searchtitle" : keywords,
- "keywords" : keywords,
- "is_unanswered" : False,
- "is_search": True,
- "search_uri": search_uri,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
- else:
- raise Http404
-
def search(request): #generates listing of questions matching a search query - including tags and just words
"""redirects to people and tag search pages
todo: eliminate this altogether and instead make
@@ -258,7 +177,6 @@ def search(request): #generates listing of questions matching a search query - i
else:
raise Http404
-
def tag(request, tag):#stub generates listing of questions tagged with a single tag
return questions(request, tagname=tag)
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index 65af2080..cac204e1 100644
--- a/locale/en/LC_MESSAGES/django.mo
+++ b/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 23e1d410..e773b46f 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -1690,7 +1690,7 @@ msgstr ""
#: forum/skins/default/templates/header.html:29
#: forum/skins/default/templates/header.html:57
msgid "users"
-msgstr ""
+msgstr "people"
#: forum/skins/default/templates/header.html:31
msgid "books"
diff --git a/settings.py b/settings.py
index c8098df9..4d799a13 100644
--- a/settings.py
+++ b/settings.py
@@ -31,6 +31,7 @@ MIDDLEWARE_CLASSES = (
#'recaptcha_django.middleware.ReCaptchaMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
+ 'forum.middleware.view_log.ViewLogMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (