2
\$\begingroup\$

I'm trying to do some basic math comparing some course groupings to completed courses. The original code shown below seems huge and maybe not pythonic?

I'm trying to write better view code then what I currently have. The reason? I want to improve and secondly, I'm checking manually for each and every course. There has to be a better way than what I have, e.g. complted_percent4 = [micro for micro in completed_assessments if micro == 4]

Background: In total there are 5 courses (at the moment) that have their own subset of episodes. Think of it in terms of a series and episodes (Series 1 has episodes 1a-1f and Series 2 has episodes 2a-2g).

NEW More Background on the tables -- this is a Entity-Relationship Database Table : Assessments (a class/table) is the actual courses that holds microseries along with assessment name and date... etc. Assessment_Results hold the name of the Assessment Class and the User class. User is all the normal elements you would find... name, email, etc.

API Based on CRUD operations.
user_results = api.retrieve_assessment_results() which you see being access here: result.assessment.microseries is a query in sqlalchemy that returns a list of ALL the assessment and results found by that user. I used dot notation to access them specifically. Microseries was something I introduced to chunk together all the episodes of one series.

What an assessment list looks like: (printed from all_assessments):
[<Assessment(name='Episode 1A', text='myotomy 1A', microseries='1')>, <Assessment(name='Episode 1B', text='Myotomy 1B', microseries='1')>, <Assessment(name='Episode 1C', text='Myotomy 1C', microseries='1')>, <Assessment(name='Episode 1D', text='Myotomy 1D', microseries='1')>, <Assessment(name='Episode 2A', text='Resection 2A', microseries='2')>, <Assessment(name='Episode 2B', text='Resection 2B', microseries='2')>, <Assessment(name='Episode 2C', text='Resection 2C', microseries='2')>,...

Any guidance would be appreciated, especially insight on better practices as I am a newbie.

**Using: Python 2.7, Pyramid, JINJA2 templates (with HTML) and SQLAlchemy


Current working code:

@view_config(route_name='assessments', request_method='GET', renderer='templates/unique_assessments.jinja2', permission='create')
def view_unique_microseries_group(request):
    logged_in_userid  = authenticated_userid(request)
    if logged_in_userid is None:
        raise HTTPForbidden()
    user_results = api.retrieve_assessment_results() #all user results in a list
    all_assessments = api.retrieve_assessments() # all assessments in a list
    all_microseries = api.retrieve_microseries() # all microseries in a list

    completed_assessments = []
    microseries_list = []

    for assessment in all_assessments:
        if assessment is None: #assumes none
            continue

        found_assessment_result = False
        for result in user_results:
            if result.owner.username == logged_in_userid and result.assessment == assessment:
                found_assessment_result = True # assign Boolean statement
                break # no need to check further

        if found_assessment_result:
            completed_assessments.append(result.assessment.microseries)

        for micro in all_microseries:
            if micro[0] == assessment.microseries:
                microseries_list.append(micro[0])
                break

    complted_percent1 = [micro for micro in completed_assessments if micro == 1]
    pending_percent1 = [micro for micro in microseries_list if micro == 1]
    course_completion_percent1 = 100.0 * len(complted_percent1) / len(pending_percent1)

    complted_percent2 = [micro for micro in completed_assessments if micro == 2]
    pending_percent2 = [micro for micro in microseries_list if micro == 2]
    course_completion_percent2 = 100.0 * len(complted_percent2) / len(pending_percent2)

    complted_percent3 = [micro for micro in completed_assessments if micro == 3]
    pending_percent3 = [micro for micro in microseries_list if micro == 3]
    course_completion_percent3 = 100.0 * len(complted_percent3) / len(pending_percent3)

    complted_percent4 = [micro for micro in completed_assessments if micro == 4]
    pending_percent4 = [micro for micro in microseries_list if micro == 4]
    course_completion_percent4 = 100.0 * len(complted_percent4) / len(pending_percent4)

    complted_percent5 = [micro for micro in completed_assessments if micro == 5]
    pending_percent5 = [micro for micro in microseries_list if micro == 5]
    course_completion_percent5 = 100.0 * len(complted_percent5) / len(pending_percent5)

    return {
    'completed_assessments': completed_assessments, 'logged_in': logged_in_userid, 'course_completion_percent1': round(course_completion_percent1), 'course_completion_percent2': round(course_completion_percent2), 'course_completion_percent3': round(course_completion_percent3), 'course_completion_percent4': round(course_completion_percent4), 'course_completion_percent5': round(course_completion_percent5)
    }

Snippet of HTML to render these percentages:

        <h4>Series 1</h4>
        <p class="tag">Contains Episodes 1A-1F </p>
        <div class="progress">
            <div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: {{course_completion_percent1}}%;"> {{course_completion_percent1}}%
            </div>
        </div>
        <p class="text_column"></p>
    </div>
</div><!-- /.col-->
\$\endgroup\$
  • \$\begingroup\$ Nice question. In the future it is better to say something about what the code does in the title, as we all want to make our code better on this site. I have made an edit to it, feel welcome to review/change it if you like. \$\endgroup\$ – Phrancis Mar 3 '16 at 20:22
  • \$\begingroup\$ result.assessment.microseries contains the microseries (e.g. 1 or 2) of the user. Assessment_Results hold the user and assessment. \$\endgroup\$ – thesayhey Mar 3 '16 at 20:42
  • 1
    \$\begingroup\$ @oliverpool I added more clarification above :) \$\endgroup\$ – thesayhey Mar 3 '16 at 20:48
  • \$\begingroup\$ @thesayhey perfect: we can now delete our (obsolete) comments :-) \$\endgroup\$ – oliverpool Mar 3 '16 at 21:02
1
\$\begingroup\$

I find your variable naming quite good (but it took me time to really understand the goal or your code).

Code simplification

I consider that user_results should only contain the results of the user (or better: its assessments):

all_user_results = api.retrieve_assessment_results()
user_assessments = [res.assessment for res in all_user_results if res.owner.username == logged_in_userid]

The rest of the code can be drastically simplified with the use of Counter:

from collections import Counter
user_microseries = [assess.microseries for assess in user_assessments]
user_microseries_count = dict(Counter(user_microseries))

The user_microseries_count should be a dict like this:

user_microseries_count = {
  1: 5,
  2: 1,
  4: 3,
}

The key is the microserie and the value is the number of series done by the user. For microseries without any result, you will need a default value (0 here: user_microseries_count.get(microserie, 0).

With a similar technique you can have the total number of microseries.

all_microseries = [assessment.microseries for assessment in all_assessments]
microseries_max = dict(Counter(all_microseries))

And then the user percentage:

user_percent = {micro: 100*user_microseries_count.get(micro, 0)/microseries_max[micro] for micro in microseries_max}

Should give:

user_percent = {
  1: 50,
  2: 10,
  3: 0,
  4: 30,
}

Which you can directly retrieve in your template with a getter

\$\endgroup\$
  • \$\begingroup\$ This is very cleaver and cleaner. Question, does this work with view_code is it good practice to have smaller functions? Would I just call the function in the view code that is rendering the template? Thank you again. I want to ensure I'm doing the right things here :) \$\endgroup\$ – thesayhey Mar 3 '16 at 20:58
  • \$\begingroup\$ @thesayhey I updated my answer quite a bit, because Counter is really powerful! \$\endgroup\$ – oliverpool Mar 3 '16 at 21:01
  • \$\begingroup\$ Thank you! I've never used Counter but I'm new to this. So I'll rework my code when I get back. Thanks for the insight and recommendations ! \$\endgroup\$ – thesayhey Mar 3 '16 at 21:03
  • \$\begingroup\$ @thesayhey if you want a review on your updated code, please open a new question (with a link to this one) i.e.: don't update this question with your new code :-) \$\endgroup\$ – oliverpool Mar 3 '16 at 21:07
  • 1
    \$\begingroup\$ Did you compute the variable microseries_max before? (microseries_max = dict(Counter(all_microseries))) \$\endgroup\$ – oliverpool Mar 9 '16 at 6:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.