Ejemplo n.º 1
0
 def post(self):
     per_page = app.config['SEARCH_RESULTS_PER_PAGE']
     args = search_parser.parse_args()
     args = dict([(key, value) for (key, value) in args.items()
                  if key is not None])
     if 'page' not in args:
         abort(400, message="must specify page")
     if not any(key in args
                for key in ['student_id', 'team_id', 'project_id']):
         abort(400, message="must have at least one query field")
     students = Student.objects(
         db.Q(team_id=args.get('team_id'))
         | db.Q(guc_id=args.get('guc_id')))
     if 'project_id' in args:
         if len(students) > 0:
             subs = (Submission.objects(
                 submitter__in=students,
                 project=Project.objects(id=args['project_id'])).order_by(
                     '-created_at').paginate(args['page'], per_page))
         else:
             subs = (Submission.objects(project=Project.objects(
                 id=args['project_id'])).order_by('-created_at').paginate(
                     args['page'], per_page))
     else:
         subs = Submission.objects(
             submitter__in=students).order_by('-created_at').paginate(
                 args['page'], per_page)
     return mongo_paginate_to_dict(subs, 'submissions')
Ejemplo n.º 2
0
    def test_submission_create(self):
        sub1 = Submission().create(
            **{
                "title": "title",
                "email": "email",
                "card_id": "card_id",
                "card_url": "card_url"
            })

        assert Submission().get_by_id(sub1.id)
Ejemplo n.º 3
0
 def test_submission_card_url_not_nullable(self):
     with pytest.raises(IntegrityError):
         sub1 = Submission().create(**{
             "title": "title",
             "email": "email",
             "card_id": "card_id"
         })
Ejemplo n.º 4
0
def seed_db():
    # seed the database with fake data
    with app.app_context():
        # create users
        u1 = User(name="Jami", email="*****@*****.**", password="******")
        u2 = User(name="Samyo", email="*****@*****.**", password="******")

        save_all([u1, u2])

        # follow
        u1.follow(u2)
        u2.follow(u1)

        # create and tag challenges
        c1 = Challenge(question="Who invented Python?", author=u1)
        c1.tags.append(u2)
        c1.save()

        # challenge submissions
        s = Submission(user=u2, challenge=c1, answer="Guido Van Rossum")

        save_all([u1, u2, c1, s])

        db.session.commit()
        print("Done")
Ejemplo n.º 5
0
 def get(self, id):
     """
     Lists all grades related to the user.
     """
     user = User.objects.get_or_404(id=id)
     return [
         sub.to_dict() for sub in Submission.objects(
             submitter=user).order_by('-created_at')
     ]
Ejemplo n.º 6
0
def extract_card_email(_id):
    """Forcibly extract the Trello card email address.

    The Trello card email address allows submitters to directly reply to comment notifications.
    Trello does NOT provide this information via the API.
    See: https://stackoverflow.com/questions/42247377/trello-api-e-mail-address-of-my-card-returns-null  # noqa: E501

    So we have to do good 'ole browser emulation

    Args:
        _id (int): the submission id

    """
    session = requests.Session()

    # Load the login page to capture the dsc cookie
    session.get("https://trello.com/login")

    # Authenticate using email and password
    auth_response = session.post(
        "https://trello.com/1/authentication",
        data={
            "factors[user]": current_app.config["TRELLO_USERNAME"],
            "factors[password]": current_app.config["TRELLO_PASSWORD"],
            "method": "password",
        },
    )

    # Perform authorization step
    session.post(
        "https://trello.com/1/authorization/session",
        data={
            "authentication": auth_response.json()["code"],
            "dsc": session.cookies["dsc"]
        },
    )

    # Fetch the card JSON
    submission = Submission().get_by_id(_id)
    response = session.get(submission.card_url + ".json")

    # Extract the special email and save it to our database record
    submission.card_email = response.json()["email"]
    Submission().save(submission)
Ejemplo n.º 7
0
    def test_submission_status_default(self):
        sub1 = Submission().create(
            **{
                "title": "title",
                "email": "email",
                "card_id": "card_id",
                "card_url": "card_url"
            })

        assert sub1.status == Status.NEW.value
Ejemplo n.º 8
0
def add_submission(challenge_id):
    data = request.get_json()
    challenge = Challenge.query.get(challenge_id)

    if challenge is None:
        return NotFound("Challenge not found")

    current_time = datetime.utcnow()

    if current_time >= challenge.expires_on:
        return {"message": "Challenge expired. Cannot accept answers now"}, 400

    user = User.query.get(get_jwt_identity())

    submission = Submission(challenge=challenge,
                            user=user,
                            answer=data["answer"])
    submission.save()

    return submission.serialize(), 201
Ejemplo n.º 9
0
def submit():
    """Submission form page.

    Renders the submission application form and handles form POSTs as well.

    Returns:
        render_template: if page is being called by GET, or form had errors
        redirect: when form is successfully ingested (this redirects and clears form)

    Todo:
        * add Title to Submission object (waiting on update to model)
        * need simple notification for successful form POST, currently no feedback
    """
    form = SubmissionForm()

    if form.validate_on_submit():
        client = SubmissionsTrelloClient()
        labels = client.labels

        card = client.new_submissions_list.add_card(
            name=form.data["title"],
            desc=f"""#DESCRIPTION
{form.data['description']}

#PITCH
{form.data['pitch']}""",
            labels=[
                labels[form.data["format"]], labels[form.data["audience"]]
            ],
            position="bottom",
            assign=[current_app.config["TRELLO_ASSIGNEE"]],
        )

        submission = Submission().create(
            title=form.data["title"],
            email=form.data["email"],
            card_id=card.id,
            card_url=card.url,
            description=form.data["description"],
            pitch=form.data["pitch"],
            notes=form.data["notes"],
        )

        # message Celery to create the webhook
        create_hook.apply_async(args=[submission.id, submission.card_id])

        # message Celery to fetch the card email address
        extract_card_email.apply_async(args=[submission.id])

        # reset form by redirecting back and apply url params
        return redirect(
            url_for("bp.submit", success=1, id=card.id, url=card.url))

    return render_template("submit.html", form=form)
Ejemplo n.º 10
0
 def get(self, course_name, name, page=1):
     try:
         course = Course.objects.get(name=course_name)
     except:
         abort(404, message="Course not found")
     try:
        project = Project.objects.get(name=name, course=course)
     except: 
         abort(404, message="Project not found")
     per_page = api.app.config['SUMBISSIONS_PAGE_SIZE']
     if isinstance(g.user, Student) and project.published:
         # Filter all submissions            
         subs = Submission.objects(submitter=g.user,
             project=project).order_by('-created_at').paginate(page,
             per_page)
         return mongo_paginate_to_dict(subs, 'submissions')
     elif g.user in course.teachers:
         # No need to filter
         subs = Submission.objects(project=project).order_by('-created_at').paginate(page, per_page)
         return mongo_paginate_to_dict(subs, 'submissions')
     else:
         abort(403, message="You are not a student or course teacher.")  # not a student and not a course teacher
Ejemplo n.º 11
0
def create_hook(_id, card):
    """Create Webhook for Trello Card.

    When a card is created after a submission, we need to fix it with a webhook back to  our app.

    Args:
        _id (int): the submission id
        card(str): the trello id for the card

    Todo:
        * No Exception handling!
        * should this handle submission not found?
    """
    client = SubmissionsTrelloClient()
    submission = Submission().get_by_id(_id)
    webhook = client.create_hook(current_app.config["TRELLO_HOOK"], card)
    Submission().update(submission, hook=webhook.id)

    # send confirmation email
    send_email.apply_async(
        args=[submission.id,
              Status(submission.status).name.lower()])
Ejemplo n.º 12
0
def junit_task(submission_id):
    submission = junit_actual(submission_id)
    if submission is not None:
        for sub in [
                s for s in Submission.objects(submitter=submission.submitter,
                                              project=submission.project,
                                              processed=True).order_by(
                                                  '-created_at')[10:]
        ]:
            app.logger.info(
                'Deleting submission id {0}, submitter {1}, project {2}'.
                format(sub.id, sub.submitter.name, sub.project.name))
            sub.delete()
            app.logger.info('Submission deleted')
Ejemplo n.º 13
0
def handle_submission_state_changed(data, action):

    send = False
    lists = {}

    # Create a lookup table by symbolic list name
    for local_list in TrelloList().all():
        lists[local_list.list_symbolic_name] = local_list.list_id

    submission = Submission().first(card_id=data["model"]["id"])
    if submission:
        # if card moved from NEW to IN-REVIEW
        if (action["data"]["listAfter"]["id"] == lists["REVIEW"]
                and action["data"]["listBefore"]["id"] == lists["NEW"]
                and submission.status == Status.NEW.value):
            Submission().update(submission, status=Status.INREVIEW.value)
            current_app.logger.info(
                f"Submission {submission.id} is now IN-REVIEW")
            send = True

        # if card moved from IN-REVIEW to SCHEDULED
        elif (action["data"]["listAfter"]["id"] == lists["SCHEDULED"]
              and action["data"]["listBefore"]["id"] == lists["REVIEW"]
              and submission.status == Status.INREVIEW.value):
            Submission().update(submission, status=Status.SCHEDULED.value)
            current_app.logger.info(
                f"Submission {submission.id} is now SCHEDULED")
            send = True

        # if the card has been updated, send an email
        if send:
            send_email.apply_async(
                args=[submission.id,
                      Status(submission.status).name.lower()])
    else:
        current_app.logger.error("Submission not found for Card: {}".format(
            data["model"]["id"]))
Ejemplo n.º 14
0
    def test_submit_post(self, client, mocker):
        mocker.patch("application.routes.SubmissionsTrelloClient",
                     new=MockTrelloClient)
        data = dict(
            email="*****@*****.**",
            title="foo",
            pitch="foo",
            format="IN-DEPTH",
            audience="INTERMEDIATE",
            description="foo",
            notes="foo",
        )

        # https://stackoverflow.com/questions/37579411/testing-a-post-that-uses-flask-wtf-validate-on-submit
        resp = client.post("/submit", data=data, follow_redirects=True)

        # make sure it's successful
        assert resp.status_code == 200

        # make sure the object was saved
        assert Submission().first(email="*****@*****.**")
Ejemplo n.º 15
0
def handle_comment_received(data, action):
    submission = Submission().first(card_id=data["model"]["id"])

    if submission:
        current_app.logger.info(
            f"Comment received on Submission {submission.id}")

        template_params = {
            "comment": action["display"]["entities"]["comment"]["text"],
            "name": action["memberCreator"]["fullName"],
        }

        if action["memberCreator"]["id"] != current_app.config[
                "TRELLO_ASSIGNEE"]:
            # Only send an email when a comment is left by someone other than the submitter
            # Organizers themselves should have notifications enabled for the whole board, so
            # they don't really need to get another email notification.  This also prevents
            # a noisy email back to the submitter when they reply to a comment by email.
            send_email.apply_async(
                args=[submission.id, "comment", template_params])
    else:
        current_app.logger.error("Submission not found for Card: {}".format(
            data["model"]["id"]))
Ejemplo n.º 16
0
    def post(self, course_name, name, page=1):
        """Creates a new submission."""
        course = Course.objects.get_or_404(name=course_name)
        project = Project.objects.get_or_404(name=name)
        if len(Submission.objects(project=project, submitter=g.user, processed=False)) > 4:
            abort(429, message="Too many pending submissions") 
        if not g.user in course.students:
            abort(403, message="Must be a course student to submit")
        if not project in course.projects:
            abort(404, message="Project not found.")
        if not project.can_submit:
            abort(498, message="Due date has passed, tough luck!")
        if project.is_quiz:
            # Verify verification code
            args = submission_parser.parse_args()
            if g.user.verification_code != args['verification_code']:
                abort(400, message="Invalid verification code.")
                

        if len(request.files.values()) == 1:
            subm = Submission(submitter=g.user, project=project)
            for file in request.files.values():
                if allowed_code_file(file.filename):
                    grid_file = db.GridFSProxy()
                    grid_file.put(
                        file, filename=secure_filename(file.filename), content_type=file.mimetype)
                    subm.code = grid_file
                else:
                    abort(400, message="Only {0} files allowed".format(','.join(api.app.config['ALLOWED_CODE_EXTENSIONS'])))
            subm.save()
            project.submissions.append(subm)
            project.save()
            if api.app.config['DELETE_SUBMISSIONS']:
                junit_task.delay(str(subm.id))
            else:
                junit_no_deletion.delay(str(subm.id))
            return marshal(subm.to_dict(parent_course=course, parent_project=project), submission_fields), 201
        else:
            abort(400, message="Can only submit one file.")  # Bad request
Ejemplo n.º 17
0
def send_email(self, _id, template_name, template_params=None):
    """Sends an email

    Currently configured to send emails for updates to submission statuses

    Args:
        self: this task is bound to allow access to self
        _id (int): the submission id
        email(str): email address of the intended recipient
        template_name(str): the name of the template to use for this email. Must be located in
            templates/email directory.

    Todo:
        * use send_at to send emails at a updates at reasonable times
        * should this handle submission not found?
        * add attachment in Scheduled email.

    """
    if not template_params:
        template_params = {}

    # logger = get_task_logger(__name__)

    # subject lines for emails based on submission status
    # not the best place for this, I admit.
    SUBJECTS = {
        "new": "Talk Submission Received",
        "inreview": "Talk Submission In-Review",
        "scheduled": "🎉 CONGRATS! Talk Submission Accepted 🎉",
        "comment": "Reply to Comments",
    }

    submission = Submission().get_by_id(_id)

    sg = SendGridAPIClient(apikey=current_app.config["SENDGRID_API_KEY"])

    if not template_name:
        # No template specified, use the submission status to determine what we're sending
        template_name = Status(submission.status).name.lower()

    template = f"email/{template_name}.html"

    mail = Mail(
        Email(current_app.config["SENDGRID_DEFAULT_FROM"]),
        SUBJECTS[template_name],
        Email(submission.email),
        Content(
            "text/html",
            (render_template(template,
                             title=SUBJECTS[template_name],
                             submission=submission,
                             **template_params)),
        ),
    )

    mail.personalizations[0].add_cc(
        Email(current_app.config["SENDGRID_DEFAULT_FROM"]))

    if template_name == "comment" and submission.card_email:
        # Add the card itself as the reply-to address.  When the user replies, it will create a
        # comment on the card via the Organizers account
        mail.reply_to = Email(submission.card_email)

    # mail.add_attachment(build_attachment())
    # mail.send_at = 1443636842

    sg.client.mail.send.post(request_body=mail.get())
Ejemplo n.º 18
0
    def test_base_methods(self):
        # create calls save and new
        sub1 = Submission().create(
            **{
                "title": "title",
                "email": "email",
                "card_id": "card_id",
                "card_url": "card_url",
                "card_email": "card_email",
                "description": "description",
                "notes": "notes",
                "pitch": "pitch",
            })

        assert Submission().get_by_id(sub1.id)
        assert Submission().first(email="email")

        Submission().update(sub1, card_id="new_card_id")
        assert Submission().find(card_id="new_card_id")

        assert Submission().all() is not None
        assert sub1 in Submission().get_all(sub1.id)
        assert Submission().get_or_404(sub1.id) is not None
        assert sub1.to_dict() == {
            "title": sub1.title,
            "card_id": sub1.card_id,
            "card_url": sub1.card_url,
            "created_at": sub1.created_at,
            "updated_at": sub1.updated_at,
            "type": "Submission",
            "email": sub1.email,
            "hook": sub1.hook,
            "status": sub1.status,
            "card_email": sub1.card_email,
            "description": sub1.description,
            "notes": sub1.notes,
            "pitch": sub1.pitch,
        }

        with pytest.raises(ValueError):
            sub1._isinstance(str())

        Submission().delete(sub1)
        assert Submission().get_by_id(sub1.id) is None