コード例 #1
0
    def post(self, *args, **kwargs):
        """Ensures the user is logged in and rate-limits requests if
        self.is_rate_limited() is truthy.  Then calls self.handle_action() to
        execute the requested action.
        """
        # ensure the user is logged in (UI shouldn't let tags/favorites/flags be set w/o being logged in)
        sess = is_logged_in()
        if not sess:
            #return self.do_output('error-login-required')
            #TODO: TMP!!
            uid = 'fakeuid'
        else:
            uid = sess['my_id']

        # rate limit actions to guard against bots / malicious users
        if self.is_rate_limited():
            rl = RL.rate_limit(uid)
        else:
            rl = RL_HANDLE_NORMALLY  # not rate limited
        if rl == RL_DROP:
            logging.warn("RL_DROP: %s attempt by %s" % (self.get_action_name(), uid))
            self.do_output('captcha-show')
            return  # drop this request
        elif rl == RL_HANDLE_BUT_SEND_CAPTCHA:
            logging.info("RL_HANDLE_BUT_SEND_CAPTCHA: %s attempt by %s" % (self.get_action_name(), uid))
            self.do_output('captcha-show')
        else:
            # empty response means the action was processed
            self.do_output('')

        # perform the action
        self.handle_action(uid, *args, **kwargs)
コード例 #2
0
ファイル: UserProfileEdit.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return

        req = self.request
        errors = {}
        dn = validate_string(req, errors, 'dname', 'display name', 100)
        email = validate_string(req, errors, 'email', 'email', 100)

        if len(errors):
            return self.redirect_to_self(errors)

        uid = session['my_id']
        user = User.get_by_key_name(uid)
        if user.display_name!=dn or user.email!=email:
            str_update = "dn=%s email=%s ==> dn=%s email=%s" % (user.display_name, user.email, dn, email)
            user.display_name = dn
            user.email = email
            try:
                user.put()
            except Exception, e:
                logging.info("Unable to update user profile: " + str_update)
                return self.redirect_to_self({'err':'Unable to update your profile.  Please try again later'})

            # profile has been updated
            session['my_dname'] = dn
            session['my_email'] = email
            logging.info("Updated user profile: " + str_update)
            memcache.delete('u+c%s' % uid) # clear saved user_info
            self.redirect('/profile/update?info=Your%20profile%20has%20been%20updated.')
コード例 #3
0
ファイル: Action.py プロジェクト: dound/CraigNotes
    def post(self, *args, **kwargs):
        """Ensures the user is logged in and rate-limits requests if
        self.is_rate_limited() is truthy.  Then calls self.handle_action() to
        execute the requested action.
        """
        # ensure the user is logged in (UI shouldn't let tags/favorites/flags be set w/o being logged in)
        sess = is_logged_in()
        if not sess:
            # return self.do_output('error-login-required')
            # TODO: TMP!!
            uid = "fakeuid"
        else:
            uid = sess["my_id"]

        # rate limit actions to guard against bots / malicious users
        if self.is_rate_limited():
            rl = RL.rate_limit(uid)
        else:
            rl = RL_HANDLE_NORMALLY  # not rate limited
        if rl == RL_DROP:
            logging.warn("RL_DROP: %s attempt by %s" % (self.get_action_name(), uid))
            self.do_output("captcha-show")
            return  # drop this request
        elif rl == RL_HANDLE_BUT_SEND_CAPTCHA:
            logging.info("RL_HANDLE_BUT_SEND_CAPTCHA: %s attempt by %s" % (self.get_action_name(), uid))
            self.do_output("captcha-show")
        else:
            # empty response means the action was processed
            self.do_output("")

        # perform the action
        self.handle_action(uid, *args, **kwargs)
コード例 #4
0
ファイル: SearchDelete.py プロジェクト: dound/CraigNotes
    def get(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)
        feed_key_name = self.request.get('f')
        if not feed_key_name:
            return self.redirect('/tracker')

        # remove the feed from this user's record
        user = User.get_by_key_name(session['my_id'])
        n = len(user.feeds)
        for i in xrange(len(user.feeds)):
            if user.feeds[i] == feed_key_name:
                user.feed_names = user.feed_names[:i-1] + user.feed_names[i+1:]
                user.feeds = user.feeds[:i-1] + user.feeds[i+1:]
                break
        user.put()
        if n > len(user.feeds):
            self.redirect('/tracker?info=Success', 30*60)

            # clear the memcache entry for this users' feeds
            mckey = "user-feeds:%s" % session['my_id']
            memcache.delete(mckey)
        else:
            self.redirect('/tracker?info=The%20feed%20you%20asked%20to%20stop%20tracking%20was%20not%20being%20tracked.')
コード例 #5
0
ファイル: SearchNew.py プロジェクト: dound/CraigNotes
    def get(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(MakoLoader.render('search_new.html', request=self.request))
コード例 #6
0
ファイル: User.py プロジェクト: dound/CraigNotes
def get_feed_infos(handler):
    """Returns an array of 2-tuples (feed_name, feed_key) for the current user,
    or False on failure.
    """
    session = is_logged_in(handler)
    if not session:
        return False
    uid = session['my_id']

    mckey = "user-feeds:%s" % uid
    feed_infos = memcache.get(mckey)
    if not feed_infos:
        user = User.get_by_key_name(uid)
        if not user:
            logging.error('cannot find the profile for a logged in user (%s)' % uid)
            session.terminate()
            return False
        feed_keys = [db.Key.from_path('Feed', f) for f in user.feeds]
        if feed_keys:
            feeds = db.get(feed_keys)
        else:
            feeds = []
        feed_infos = zip(user.feed_names, feeds)
        memcache.set(mckey, feed_infos, 30*60)
    return feed_infos
コード例 #7
0
ファイル: User.py プロジェクト: dound/CraigNotes
def get_feed_infos(handler):
    """Returns an array of 2-tuples (feed_name, feed_key) for the current user,
    or False on failure.
    """
    session = is_logged_in(handler)
    if not session:
        return False
    uid = session['my_id']

    mckey = "user-feeds:%s" % uid
    feed_infos = memcache.get(mckey)
    if not feed_infos:
        user = User.get_by_key_name(uid)
        if not user:
            logging.error('cannot find the profile for a logged in user (%s)' %
                          uid)
            session.terminate()
            return False
        feed_keys = [db.Key.from_path('Feed', f) for f in user.feeds]
        if feed_keys:
            feeds = db.get(feed_keys)
        else:
            feeds = []
        feed_infos = zip(user.feed_names, feeds)
        memcache.set(mckey, feed_infos, 30 * 60)
    return feed_infos
コード例 #8
0
ファイル: TrackAd.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        ad_url = validate_string(req, errors, 'ad_url', 'Craigslist Ad URL')
        if ad_url:
            if ad_url[:7] != 'http://':
                ad_url = 'http://' + ad_url
            m = RE_URL_CHECK.match(ad_url)
            if not m:
                errors['ad_url'] = 'This URL does not appear to be a valid craigslist.org webpage.'
            else:
                m = RE_ID.match(ad_url)
                if not m:
                    errors['ad_url'] = 'Could not extract the ID from Ad URL'
                else:
                    cid = int(m.group(1))
        if len(errors):
            return self.redirect_to_self(GET_PARAMS, errors)

        # efficiency: get Ad and UserCmt at the same time
        to_put = []
        ad_key = db.Key.from_path('Ad', cid)
        cmt_key = db.Key.from_path('UserCmt', '%s%s' % (session['my_id'], cid))
        ad, cmt = db.get([ad_key, cmt_key])

        # download the ad if we don't already have it in our db
        if not ad:
            ret = self.fetch_and_parse_page(ad_url)
            if not ret:
                errors['ad_url'] = 'Unable to download the webpage'
                return self.redirect_to_self(GET_PARAMS, errors)
            title, desc, dt = ret
            ad = Ad(key=ad_key, feeds=['manual'], title=title, desc=desc, update_dt=dt, url=ad_url)
            to_put = [ad]
        elif 'manual' not in ad.feeds:
            ad.feeds.insert(0, 'manual')
            to_put = [ad]

        # create UserCmt
        if not cmt:
            cmt = UserCmt(key=cmt_key, feeds=ad.feeds)
            to_put.append(cmt)
        elif 'manual' in cmt.feeds:
            return self.redirect('/tracker?info=You%20are%20already%20manually%20tracking%20that%20ad.')
        elif cmt.feeds != ad.feeds:
            cmt.feeds = ad.feeds
            to_put.append(cmt)

        # save the new entities
        if to_put:
            db.put(to_put)

        # redirect the user to the feed page
        self.redirect('/tracker?info=Added%20Ad%20%23' + str(cid) + '%20to%20your%20manually%20specified%20list.')
コード例 #9
0
ファイル: SearchNew.py プロジェクト: dound/CraigNotes
    def get(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(
            MakoLoader.render('search_new.html', request=self.request))
コード例 #10
0
ファイル: UserProfileEdit.py プロジェクト: dound/CraigNotes
    def get(self):
        session = is_logged_in(self)
        if not session:
            return

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(MakoLoader.render('userprofile_edit.html', request=self.request,
                                                  display_name=session['my_dname'], email=session['my_email']))
コード例 #11
0
ファイル: SearchNew.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        name = validate_string(req, errors, 'name', 'search name', MAX_FEED_NAME_LEN)
        if not name:
            name = ''

        rss_url = req.get('rss_url')
        if rss_url:
            feed_key = parse_rss_url(rss_url)
            if not feed_key:
                return self.redirect_to_errors(GET_PARAMS, {'error_rss_url':'''This URL isn't in the expected form.  Please <a href="/contact">send it to us</a> if you think this is a bug.'''})
            if len(errors):
                return self.redirect_to_self(GET_PARAMS, errors)
        else:
            city = validate_string(req, errors, 'city', 'city/region', max_len=50)
            category = validate_string(req, errors, 'category', 'category', 3)
            area = '' # TODO: add area picker
            if not CATEGORIES.has_key(category):
                errors['category'] = 'Please choose a category.'
            query = validate_string(req, errors, 'query', 'search string', 100, required=False)
            if not query:
                query = ''
            title_only = req.get('title_only')=='checked'
            if title_only:
                stype = 'T'
            else:
                stype = 'A'
            min_cost = validate_int(req, errors, 'min_cost', 'Minimum Cost', 0, None, False)
            if not min_cost:
                min_cost = ''
            max_cost = validate_int(req, errors, 'max_cost', 'Maximum Cost', 0, None, False)
            if not max_cost:
                max_cost = ''
            num_bedrooms = validate_int(req, errors, 'num_bedrooms', 'Number of bedrooms', 1, 8, False)
            if not num_bedrooms:
                num_bedrooms = ''
            cats = req.get('cats')=='checked'
            dogs = req.get('dogs')=='checked'
            pics = req.get('pics')=='checked'
            if len(errors):
                return self.redirect_to_self(GET_PARAMS, errors)
            feed_key = Feed.make_key_name(city, category, area, min_cost, max_cost,
                                          num_bedrooms, cats, dogs, pics, [], stype, query)

        # make sure the feed is in the datastore
        try:
            feed = Feed.get_or_insert(key_name=feed_key)
        except Exception, e:
            logging.error('Unable to create new Feed (%s): %s' % (feed_key, e))
            return self.redirect_to_self(GET_PARAMS, {'err':'The service is temporarily unavailable - please try again later.'})
コード例 #12
0
ファイル: UserProfileEdit.py プロジェクト: dound/CraigNotes
    def get(self):
        session = is_logged_in(self)
        if not session:
            return

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(
            MakoLoader.render('userprofile_edit.html',
                              request=self.request,
                              display_name=session['my_dname'],
                              email=session['my_email']))
コード例 #13
0
ファイル: SearchRename.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        new_name = validate_string(req, errors, 'new_name', 'new search name',
                                   MAX_FEED_NAME_LEN)
        if not new_name:
            new_name = ''
        if len(errors):
            return self.redirect_to_self(GET_PARAMS, errors)

        # update the search name
        user = User.get_by_key_name(session['my_id'])
        if not user:
            logging.error(
                'Unable to retrieve user record for a logged in user: %s' %
                session['my_id'])
            return self.redirect(
                '/?err=The service is temporarily unavailable - please try again later.'
            )
        feed_key = self.request.get('f')
        if feed_key not in user.feeds:
            return self.redirect(
                "/tracker&err=You%20can't%20rename%20a%20search%20you%20aren't%20tracking."
            )
        for i in xrange(len(user.feeds)):
            if user.feeds[i] == feed_key:
                user.feed_names[i] = new_name
                break
        try:
            user.put()
        except:
            logging.error(
                'Unable to update user record for logged in user: %s' %
                session['my_id'])
            return self.redirect(
                '/tracker?err=The service is temporarily unavailable - please try again later.'
            )

        # invalidate the memcache entry for this users' feeds if it exists
        mckey = "user-feeds:%s" % session['my_id']
        feed_infos = memcache.delete(mckey)

        # redirect the user to the feed page
        self.redirect('/view?t=newest&f=%s' % urllib.quote(feed_key))
コード例 #14
0
ファイル: SearchRename.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        new_name = validate_string(req, errors, 'new_name', 'new search name', MAX_FEED_NAME_LEN)
        if not new_name:
            new_name = ''
        if len(errors):
            return self.redirect_to_self(GET_PARAMS, errors)

        # update the search name
        user = User.get_by_key_name(session['my_id'])
        if not user:
            logging.error('Unable to retrieve user record for a logged in user: %s' % session['my_id'])
            return self.redirect('/?err=The service is temporarily unavailable - please try again later.')
        feed_key = self.request.get('f')
        if feed_key not in user.feeds:
            return self.redirect("/tracker&err=You%20can't%20rename%20a%20search%20you%20aren't%20tracking.")
        for i in xrange(len(user.feeds)):
            if user.feeds[i] == feed_key:
                user.feed_names[i] = new_name
                break
        try:
            user.put()
        except:
            logging.error('Unable to update user record for logged in user: %s' % session['my_id'])
            return self.redirect('/tracker?err=The service is temporarily unavailable - please try again later.')

        # invalidate the memcache entry for this users' feeds if it exists
        mckey = "user-feeds:%s" % session['my_id']
        feed_infos = memcache.delete(mckey)

        # redirect the user to the feed page
        self.redirect('/view?t=newest&f=%s' % urllib.quote(feed_key))
コード例 #15
0
ファイル: UserProfileEdit.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return

        req = self.request
        errors = {}
        dn = validate_string(req, errors, 'dname', 'display name', 100)
        email = validate_string(req, errors, 'email', 'email', 100)

        if len(errors):
            return self.redirect_to_self(errors)

        uid = session['my_id']
        user = User.get_by_key_name(uid)
        if user.display_name != dn or user.email != email:
            str_update = "dn=%s email=%s ==> dn=%s email=%s" % (
                user.display_name, user.email, dn, email)
            user.display_name = dn
            user.email = email
            try:
                user.put()
            except Exception, e:
                logging.info("Unable to update user profile: " + str_update)
                return self.redirect_to_self({
                    'err':
                    'Unable to update your profile.  Please try again later'
                })

            # profile has been updated
            session['my_dname'] = dn
            session['my_email'] = email
            logging.info("Updated user profile: " + str_update)
            memcache.delete('u+c%s' % uid)  # clear saved user_info
            self.redirect(
                '/profile/update?info=Your%20profile%20has%20been%20updated.')
コード例 #16
0
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        ad_url = validate_string(req, errors, 'ad_url', 'Craigslist Ad URL')
        if ad_url:
            if ad_url[:7] != 'http://':
                ad_url = 'http://' + ad_url
            m = RE_URL_CHECK.match(ad_url)
            if not m:
                errors[
                    'ad_url'] = 'This URL does not appear to be a valid craigslist.org webpage.'
            else:
                m = RE_ID.match(ad_url)
                if not m:
                    errors['ad_url'] = 'Could not extract the ID from Ad URL'
                else:
                    cid = int(m.group(1))
        if len(errors):
            return self.redirect_to_self(GET_PARAMS, errors)

        # efficiency: get Ad and UserCmt at the same time
        to_put = []
        ad_key = db.Key.from_path('Ad', cid)
        cmt_key = db.Key.from_path('UserCmt', '%s%s' % (session['my_id'], cid))
        ad, cmt = db.get([ad_key, cmt_key])

        # download the ad if we don't already have it in our db
        if not ad:
            ret = self.fetch_and_parse_page(ad_url)
            if not ret:
                errors['ad_url'] = 'Unable to download the webpage'
                return self.redirect_to_self(GET_PARAMS, errors)
            title, desc, dt = ret
            ad = Ad(key=ad_key,
                    feeds=['manual'],
                    title=title,
                    desc=desc,
                    update_dt=dt,
                    url=ad_url)
            to_put = [ad]
        elif 'manual' not in ad.feeds:
            ad.feeds.insert(0, 'manual')
            to_put = [ad]

        # create UserCmt
        if not cmt:
            cmt = UserCmt(key=cmt_key, feeds=ad.feeds)
            to_put.append(cmt)
        elif 'manual' in cmt.feeds:
            return self.redirect(
                '/tracker?info=You%20are%20already%20manually%20tracking%20that%20ad.'
            )
        elif cmt.feeds != ad.feeds:
            cmt.feeds = ad.feeds
            to_put.append(cmt)

        # save the new entities
        if to_put:
            db.put(to_put)

        # redirect the user to the feed page
        self.redirect('/tracker?info=Added%20Ad%20%23' + str(cid) +
                      '%20to%20your%20manually%20specified%20list.')
コード例 #17
0
    def get(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect('/')
        uid = session['my_id']

        now = datetime.datetime.now()
        feed_key_name = self.request.get('f')
        t = self.request.get('t')
        overall_view = (not feed_key_name and t != 'newest')
        if feed_key_name == 'manual':
            fhid = 'manual'
            age = desc = None
            updating_shortly = False
            if t == 'hidden':
                name = "Manually-Added Ads that were Hidden"
            elif t == 'newest':
                return self.redirect('/tracker')
            else:
                name = "Manually-Added Ads"
        elif feed_key_name:
            fhid = Feed.hashed_id_from_pk(feed_key_name)

            # get the user's name for this feed
            name = get_search_name(self, feed_key_name)
            if name is None:
                return self.redirect('/tracker')  # user is no longer tracking this feed
            elif name is False:
                return self.redirect('/') # login related error

            # compute how old the data is
            feed_dt_updated = dt_feed_last_updated(feed_key_name)
            if not feed_dt_updated:
                return self.redirect('/tracker?err=That%20feed%20no%20longer%20exists.')
            age = str_age(feed_dt_updated, now)
            td = now - feed_dt_updated
            updating_shortly = td.days>0 or td.seconds>MAX_AGE_MIN*60
            if updating_shortly:
                age += ' - update in progress'

            # update the feed if we haven't retrieved the latest ads recently
            updating = update_feed_if_needed(feed_key_name)
            if updating is None:
                return self.redirect('/tracker?err=The%20requested%20feed%20does%20not%20exist.')
        elif overall_view:
            age = desc = fhid = None
            updating_shortly = False
            if t == 'hidden':
                name = "All Hidden Ads"
            else:
                name = "All Rated/Noted Ads"
        else:
            # t=newest and feed=all doesn't make sense together
            return self.redirect('/tracker')

        # determine which set of ads to show
        next = self.request.get('next')
        if t == 'newest':
            # show the newest ads (regardless of whether the user has commented on them or not)
            q = Ad.all().filter('feeds =', fhid).order('-update_dt')
            if next:
                q.with_cursor(next)
            ads = q.fetch(ADS_PER_PAGE)

            # get user comments on these ads, if any
            user_ad_keys = [db.Key.from_path('UserCmt', '%s%s' % (uid, a.cid)) for a in ads]
            user_ad_notes = db.get(user_ad_keys)
            title_extra = 'Newest Ads'
        else:
            # show ads this user has commented on/rated (whether to show hidden ads or not depends on t)
            hidden = (t == 'hidden')
            q = UserCmt.all()
            q.filter('uid =', session['my_id'])
            if fhid:
                q.filter('feeds =', fhid)
            if hidden:
                q.filter('dt_hidden >', DT_PRESITE).order('-dt_hidden')
            else:
                q.filter('dt_hidden =', None).order('-rating')
            if next:
                q.with_cursor(next)
            user_ad_notes = q.fetch(ADS_PER_PAGE)

            # get the ads associated with these comments
            ad_keys = [db.Key.from_path('Ad', uan.cid) for uan in user_ad_notes]
            ads = db.get(ad_keys)

            if t == 'hidden':
                title_extra = "Ignored Ads"
            else:
                title_extra = "Ads I've Rated"

        # put the ads and their comments together
        ad_infos = zip(ads, user_ad_notes)

        # check that each UserCmt.feeds field is up to date with Ad.feeds (can
        # only do this when we're searching by Ad, i.e., t=newest)
        if t == 'newest':
            # TODO: only mark as outdated if they are inequal EXCEPT 'manual'
            # TODO: when updating cmt.feeds, don't copy over 'manual' (user-specific)
            # TODO: reconsider this code ...
            outdated = [(ad,cmt) for ad, cmt in ad_infos if cmt and ad.feeds!=cmt.feeds]
            if outdated:
                # update any out of date comments
                for ad,cmt in outdated:
                    cmt.feeds = ad.feeds
                db.put([cmt for ad,cmt in outdated])

        # whether there may be more ads
        more = (len(ads) == ADS_PER_PAGE)
        if more:
            more = q.cursor()
        if not more or more==str(next):
            more = None

        # get a description of the search we're viewing
        if fhid and fhid!='manual':
            tmp_feed = Feed(key_name=feed_key_name)
            tmp_feed.extract_values()
            desc = tmp_feed.desc()

        if not next:
            page = 1
        else:
            try:
                page = int(self.request.get('page', 1))
            except ValueError:
                page = 1;

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write(MakoLoader.render('search_view.html', request=self.request,
                                                  ADS_PER_PAGE=ADS_PER_PAGE, ads=ad_infos, more=more, age=age, now=now, search_desc=desc, title_extra=title_extra, page=page, name=name, updating_shortly=updating_shortly, overall_view=overall_view))
コード例 #18
0
ファイル: SearchNew.py プロジェクト: dound/CraigNotes
    def post(self):
        session = is_logged_in(self)
        if not session:
            return self.redirect(REDIR_URL)

        req = self.request
        errors = {}

        name = validate_string(req, errors, 'name', 'search name',
                               MAX_FEED_NAME_LEN)
        if not name:
            name = ''

        rss_url = req.get('rss_url')
        if rss_url:
            feed_key = parse_rss_url(rss_url)
            if not feed_key:
                return self.redirect_to_errors(
                    GET_PARAMS, {
                        'error_rss_url':
                        '''This URL isn't in the expected form.  Please <a href="/contact">send it to us</a> if you think this is a bug.'''
                    })
            if len(errors):
                return self.redirect_to_self(GET_PARAMS, errors)
        else:
            city = validate_string(req,
                                   errors,
                                   'city',
                                   'city/region',
                                   max_len=50)
            category = validate_string(req, errors, 'category', 'category', 3)
            area = ''  # TODO: add area picker
            if not CATEGORIES.has_key(category):
                errors['category'] = 'Please choose a category.'
            query = validate_string(req,
                                    errors,
                                    'query',
                                    'search string',
                                    100,
                                    required=False)
            if not query:
                query = ''
            title_only = req.get('title_only') == 'checked'
            if title_only:
                stype = 'T'
            else:
                stype = 'A'
            min_cost = validate_int(req, errors, 'min_cost', 'Minimum Cost', 0,
                                    None, False)
            if not min_cost:
                min_cost = ''
            max_cost = validate_int(req, errors, 'max_cost', 'Maximum Cost', 0,
                                    None, False)
            if not max_cost:
                max_cost = ''
            num_bedrooms = validate_int(req, errors, 'num_bedrooms',
                                        'Number of bedrooms', 1, 8, False)
            if not num_bedrooms:
                num_bedrooms = ''
            cats = req.get('cats') == 'checked'
            dogs = req.get('dogs') == 'checked'
            pics = req.get('pics') == 'checked'
            if len(errors):
                return self.redirect_to_self(GET_PARAMS, errors)
            feed_key = Feed.make_key_name(city, category, area, min_cost,
                                          max_cost, num_bedrooms, cats, dogs,
                                          pics, [], stype, query)

        # make sure the feed is in the datastore
        try:
            feed = Feed.get_or_insert(key_name=feed_key)
        except Exception, e:
            logging.error('Unable to create new Feed (%s): %s' % (feed_key, e))
            return self.redirect_to_self(
                GET_PARAMS, {
                    'err':
                    'The service is temporarily unavailable - please try again later.'
                })