Exemplo n.º 1
0
def moderatejob(domain, hashid):
    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    if post.status in [POSTSTATUS.DRAFT, POSTSTATUS.PENDING]:
        abort(403)
    if post.status in [POSTSTATUS.REJECTED, POSTSTATUS.WITHDRAWN, POSTSTATUS.SPAM]:
        abort(410)
    moderateform = forms.ModerateForm()
    if moderateform.validate_on_submit():
        post.closed_datetime = datetime.utcnow()
        post.review_comments = moderateform.reason.data
        post.review_datetime = datetime.utcnow()
        post.reviewer = g.user
        flashmsg = "This job post has been moderated."
        post.status = POSTSTATUS.MODERATED
        msg = Message(subject="About your job post on Hasjob",
            recipients=[post.email])
        msg.html = email_transform(render_template('moderate_email.html', post=post), base_url=request.url_root)
        msg.body = html2text(msg.html)
        mail.send(msg)
        db.session.commit()
        # cache bust
        dogpile.invalidate_region('hasjob_index')
        if request.is_xhr:
            return "<p>%s</p>" % flashmsg
    elif request.method == 'POST' and request.is_xhr:
        return render_template('inc/moderateform.html', post=post, moderateform=moderateform)
    return redirect(post.url_for(), code=303)
Exemplo n.º 2
0
def board_add(board, jobpost):
    board.add(jobpost)
    db.session.commit()
    # cache bust
    dogpile.invalidate_region('hasjob_index')
    flash(u"You’ve added this job to %s" % board.title, 'interactive')
    return redirect(jobpost.url_for(subdomain=board.name))
Exemplo n.º 3
0
def pinnedjob(domain, hashid):
    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    if g.board:
        obj = post.link_to_board(g.board)
        if obj is None:
            abort(404)
    else:
        obj = post
    pinnedform = forms.PinnedForm(obj=obj)
    if pinnedform.validate_on_submit():
        obj.pinned = pinnedform.pinned.data
        db.session.commit()
        if obj.pinned:
            msg = "This post has been pinned."
        else:
            msg = "This post is no longer pinned."
        # cache bust
        dogpile.invalidate_region('hasjob_index')
    else:
        msg = "Invalid submission"
    if request.is_xhr:
        return Markup('<p>' + msg + '</p>')
    else:
        flash(msg)
        return redirect(post.url_for(), 303)
Exemplo n.º 4
0
def confirm_email(domain, hashid, key):
    # If post is in pending state and email key is correct, convert to published
    # and update post.datetime to utcnow() so it'll show on top of the stack
    # This function expects key to be email_verify_key, not edit_key like the others
    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    if post.status in POSTSTATUS.GONE:
        abort(410)
    elif post.status in POSTSTATUS.LISTED:
        flash("This job post has already been confirmed and published", "interactive")
        return redirect(post.url_for(), code=302)
    elif post.status == POSTSTATUS.DRAFT:
        # This should not happen. The user doesn't have this URL until they
        # pass the confirm form
        return redirect(post.url_for('confirm'), code=302)
    elif post.status == POSTSTATUS.PENDING:
        if key != post.email_verify_key:
            return render_template('403.html', description=u"This link has expired or is malformed. Check if you have received a newer email from us.")
        else:
            if app.config.get('THROTTLE_LIMIT', 0) > 0:
                post_count = JobPost.query.filter(JobPost.email_domain == post.email_domain).filter(
                    JobPost.status.in_(POSTSTATUS.POSTPENDING)).filter(
                        JobPost.datetime > datetime.utcnow() - timedelta(days=1)).count()
                if post_count > app.config['THROTTLE_LIMIT']:
                    flash(u"We have received too many posts with %s addresses in the last 24 hours. "
                        u"Posts are rate-limited per domain, so yours was not confirmed for now. "
                        u"Please try confirming again in a few hours."
                        % post.email_domain, category='info')
                    return redirect(url_for('index'))
            post.email_verified = True
            post.status = POSTSTATUS.CONFIRMED
            post.datetime = datetime.utcnow()
            db.session.commit()
            if app.config['TWITTER_ENABLED']:
                if post.headlineb:
                    tweet.delay(post.headline, post.url_for(b=0, _external=True),
                        post.location, dict(post.parsed_location or {}), username=post.twitter)
                    tweet.delay(post.headlineb, post.url_for(b=1, _external=True),
                        post.location, dict(post.parsed_location or {}), username=post.twitter)
                else:
                    tweet.delay(post.headline, post.url_for(_external=True),
                        post.location, dict(post.parsed_location or {}), username=post.twitter)
            add_to_boards.delay(post.id)
            flash("Congratulations! Your job post has been published. As a bonus for being an employer on Hasjob, "
                "you can now see how your post is performing relative to others. Look in the sidebar of any post.",
                "interactive")
    # cache bust
    dogpile.invalidate_region('hasjob_index')
    return redirect(post.url_for(), code=302)
Exemplo n.º 5
0
def reopen(domain, hashid, key):
    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    if not post:
        abort(404)
    if not post.admin_is(g.user):
        abort(403)
    # Only closed posts can be reopened
    if not post.is_closed():
        flash("Your job post can't be reopened.", "info")
        return redirect(post.url_for(), code=303)
    form = Form()
    if form.validate_on_submit():
        post.confirm()
        post.closed_datetime = datetime.utcnow()
        db.session.commit()
        # cache bust
        dogpile.invalidate_region('hasjob_index')
        return redirect(post.url_for(), code=303)
    return render_template("reopen.html", post=post, form=form)
Exemplo n.º 6
0
def withdraw(domain, hashid, key):
    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    form = forms.WithdrawForm()
    if not ((key is None and g.user is not None and post.admin_is(g.user)) or (key == post.edit_key)):
        abort(403)
    if post.status == POSTSTATUS.WITHDRAWN:
        flash("Your job post has already been withdrawn", "info")
        return redirect(url_for('index'), code=303)
    if post.status not in POSTSTATUS.LISTED:
        flash("Your post cannot be withdrawn because it is not public", "info")
        return redirect(url_for('index'), code=303)
    if form.validate_on_submit():
        post.status = POSTSTATUS.WITHDRAWN
        post.closed_datetime = datetime.utcnow()
        db.session.commit()
        flash("Your job post has been withdrawn and is no longer available", "info")
        # cache bust
        dogpile.invalidate_region('hasjob_index')
        return redirect(url_for('index'), code=303)
    return render_template("withdraw.html", post=post, form=form)
Exemplo n.º 7
0
def close(domain, hashid, key):
    post = JobPost.get(hashid)
    if not post:
        abort(404)
    if not post.admin_is(g.user):
        abort(403)
    if request.method == 'GET' and post.is_closed():
        return redirect(post.url_for('reopen'), code=303)
    if not post.is_public():
        flash("Your job post can't be closed.", "info")
        return redirect(post.url_for(), code=303)
    form = Form()
    if form.validate_on_submit():
        post.close()
        post.closed_datetime = datetime.utcnow()
        db.session.commit()
        # cache bust
        dogpile.invalidate_region('hasjob_index')
        return redirect(post.url_for(), code=303)
    return render_template("close.html", post=post, form=form)
Exemplo n.º 8
0
def editjob(hashid, key, domain=None, form=None, validated=False, newpost=None):
    if form is None:
        form = forms.ListingForm(request.form)
        form.job_type.choices = JobType.choices(g.board)
        form.job_category.choices = JobCategory.choices(g.board)
        if g.board and not g.board.require_pay:
            form.job_pay_type.choices = [(-1, u'Confidential')] + PAY_TYPE.items()

    post = None
    no_email = False

    if not newpost:
        post = JobPost.query.filter_by(hashid=hashid).first_or_404()
        if not ((key is None and g.user is not None and post.admin_is(g.user)) or (key == post.edit_key)):
            abort(403)

        # Once this post is published, require editing at /domain/<hashid>/edit
        if not key and post.status not in POSTSTATUS.UNPUBLISHED and post.email_domain != domain:
            return redirect(post.url_for('edit'), code=301)

        # Don't allow editing jobs that aren't on this board as that may be a loophole when
        # the board allows no pay (except in the 'www' root board, where editing is always allowed)
        with db.session.no_autoflush:
            if g.board and g.board.not_root and post.link_to_board(g.board) is None and request.method == 'GET':
                blink = post.postboards.first()
                if blink:
                    return redirect(post.url_for('edit', subdomain=blink.board.name, _external=True))
                else:
                    return redirect(post.url_for('edit', subdomain=None, _external=True))

        # Don't allow email address to be changed once it's confirmed
        if post.status in POSTSTATUS.POSTPENDING:
            no_email = True

    if request.method == 'POST' and post and post.status in POSTSTATUS.POSTPENDING:
        # del form.poster_name  # Deprecated 2013-11-20
        form.poster_email.data = post.email
    if request.method == 'POST' and (validated or form.validate()):
        form_description = bleach.linkify(bleach.clean(form.job_description.data, tags=ALLOWED_TAGS))
        form_perks = bleach.linkify(bleach.clean(form.job_perks_description.data, tags=ALLOWED_TAGS)) if form.job_perks.data else ''
        form_how_to_apply = form.job_how_to_apply.data
        form_email_domain = get_email_domain(form.poster_email.data)
        form_words = get_word_bag(u' '.join((form_description, form_perks, form_how_to_apply)))

        similar = False
        with db.session.no_autoflush:
            for oldpost in JobPost.query.filter(db.or_(
                db.and_(
                    JobPost.email_domain == form_email_domain,
                    JobPost.status.in_(POSTSTATUS.POSTPENDING)),
                JobPost.status == POSTSTATUS.SPAM)).filter(
                    JobPost.datetime > datetime.utcnow() - agelimit).all():
                if not post or (oldpost.id != post.id):
                    if oldpost.words:
                        s = SequenceMatcher(None, form_words, oldpost.words)
                        if s.ratio() > 0.6:
                            similar = True
                            break

        if similar:
            flash("This post is very similar to an earlier post. You may not repost the same job "
                "in less than %d days." % agelimit.days, category='interactive')
        else:
            if newpost:
                post = JobPost(**newpost)
                db.session.add(post)
                if g.board:
                    post.add_to(g.board)
                    if g.board.not_root:
                        post.add_to('www')

            post.headline = form.job_headline.data
            post.headlineb = form.job_headlineb.data
            post.type_id = form.job_type.data
            post.category_id = form.job_category.data
            post.location = form.job_location.data
            post.relocation_assist = form.job_relocation_assist.data
            post.description = form_description
            post.perks = form_perks
            post.how_to_apply = form_how_to_apply
            post.company_name = form.company_name.data
            post.company_url = form.company_url.data
            post.hr_contact = form.hr_contact.data
            post.twitter = form.twitter.data

            post.pay_type = form.job_pay_type.data
            if post.pay_type == -1:
                post.pay_type = None

            if post.pay_type is not None and post.pay_type != PAY_TYPE.NOCASH:
                post.pay_currency = form.job_pay_currency.data
                post.pay_cash_min = form.job_pay_cash_min.data
                post.pay_cash_max = form.job_pay_cash_max.data
            else:
                post.pay_currency = None
                post.pay_cash_min = None
                post.pay_cash_max = None
            if form.job_pay_equity.data:
                post.pay_equity_min = form.job_pay_equity_min.data
                post.pay_equity_max = form.job_pay_equity_max.data
            else:
                post.pay_equity_min = None
                post.pay_equity_max = None

            post.admins = form.collaborators.data

            # Allow name and email to be set only on non-confirmed posts
            if not no_email:
                # post.fullname = form.poster_name.data  # Deprecated 2013-11-20
                if post.email != form.poster_email.data:
                    # Change the email_verify_key if the email changes
                    post.email_verify_key = random_long_key()
                post.email = form.poster_email.data
                post.email_domain = form_email_domain
                post.md5sum = md5sum(post.email)
                with db.session.no_autoflush:
                    # This is dependent on the domain's DNS validity already being confirmed
                    # by the form's email validator
                    post.domain = Domain.get(post.email_domain, create=True)
                    if not post.domain.is_webmail and not post.domain.title:
                        post.domain.title, post.domain.legal_title = common_legal_names(post.company_name)
            # To protect from gaming, don't allow words to be removed in edited posts once the post
            # has been confirmed. Just add the new words.
            if post.status in POSTSTATUS.POSTPENDING:
                prev_words = post.words or u''
            else:
                prev_words = u''
            post.words = get_word_bag(u' '.join((prev_words, form_description, form_perks, form_how_to_apply)))

            post.language, post.language_confidence = identify_language(post)

            if post.status == POSTSTATUS.MODERATED:
                post.status = POSTSTATUS.CONFIRMED

            if request.files['company_logo']:
                # The form's validator saved the processed logo in g.company_logo.
                thumbnail = g.company_logo
                logofilename = uploaded_logos.save(thumbnail, name='%s.' % post.hashid)
                post.company_logo = logofilename
            else:
                if form.company_logo_remove.data:
                    post.company_logo = None

            db.session.commit()
            tag_jobpost.delay(post.id)    # Keywords
            tag_locations.delay(post.id)  # Locations
            post.uncache_viewcounts('pay_label')
            session.pop('userkeys', None)  # Remove legacy userkeys dict
            session.permanent = True
            # cache bust
            dogpile.invalidate_region('hasjob_index')
            return redirect(post.url_for(), code=303)
    elif request.method == 'POST':
        flash("Please review the indicated issues", category='interactive')
    elif request.method == 'GET':
        form.populate_from(post)
    return render_template('postjob.html', form=form, no_email=no_email)
Exemplo n.º 9
0
def rejectjob(domain, hashid):
    def send_reject_mail(reject_type, post, banned_posts=[]):
        if reject_type not in ['reject', 'ban']:
            return
        mail_meta = {
            'reject': {
                'subject': "About your job post on Hasjob",
                'template': "reject_email.html"
            },
            'ban': {
                'subject': "About your account and job posts on Hasjob",
                'template': "reject_domain_email.html"
            }
        }
        msg = Message(subject=mail_meta[reject_type]['subject'], recipients=[post.email])
        msg.html = email_transform(render_template(mail_meta[reject_type]['template'], post=post, banned_posts=banned_posts), base_url=request.url_root)
        msg.body = html2text(msg.html)
        mail.send(msg)

    def reject_post(post, reason, spam=False):
        post.status = POSTSTATUS.SPAM if spam else POSTSTATUS.REJECTED
        post.closed_datetime = db.func.utcnow()
        post.review_comments = reason
        post.review_datetime = db.func.utcnow()
        post.reviewer = g.user

    post = JobPost.query.filter_by(hashid=hashid).first_or_404()
    if post.status in POSTSTATUS.UNPUBLISHED and not post.admin_is(g.user):
        abort(403)
    if post.status in POSTSTATUS.GONE:
        abort(410)
    rejectform = forms.RejectForm()

    if rejectform.validate_on_submit():
        banned_posts = []
        if request.form.get('submit') == 'reject':
            flashmsg = "This job post has been rejected"
            reject_post(post, rejectform.reason.data)
        elif request.form.get('submit') == 'spam':
            flashmsg = "This job post has been marked as spam"
            reject_post(post, rejectform.reason.data, True)
        elif request.form.get('submit') == 'ban':
            # Moderator asked for a ban, so ban the user and reject
            # all posts from the domain if it's not a webmail domain
            if post.user:
                post.user.blocked = True
            if post.domain.is_webmail:
                flashmsg = "This job post has been rejected and the user banned"
                reject_post(post, rejectform.reason.data)
                banned_posts = [post]
            else:
                flashmsg = "This job post has been rejected and the user and domain banned"
                post.domain.is_banned = True
                post.domain.banned_by = g.user
                post.domain.banned_at = db.func.utcnow()
                post.domain.banned_reason = rejectform.reason.data

                for jobpost in post.domain.jobposts:
                    if jobpost.status in POSTSTATUS.LISTED:
                        reject_post(jobpost, rejectform.reason.data)
                        banned_posts.append(jobpost)
        else:
            # We're not sure what button the moderator hit
            abort(400)

        db.session.commit()
        send_reject_mail(request.form.get('submit'), post, banned_posts)
        # cache bust
        dogpile.invalidate_region('hasjob_index')
        if request.is_xhr:
            return "<p>%s</p>" % flashmsg
        else:
            flash(flashmsg, "interactive")
    elif request.method == 'POST' and request.is_xhr:
        return render_template('inc/rejectform.html', post=post, rejectform=rejectform)
    return redirect(post.url_for(), code=303)