def get(self): # First, check that the logged in user is a student student = utils.check_privilege(model.Role.student) if not student: # Redirect home if not a student return self.redirect('/home') # end # Otherwise, log which student made the get utils.log('Student logged in: ' + str(student)) # And grab the key for the section section_key = self.request.get('section') # Make sure that it isn't null if not section_key: # Error if so, and redirect home utils.error('Section_key is null') self.redirect('/home') else: # And then grab the section from the key section = ndb.Key(urlsafe=section_key).get() # Making sure it's not null if not section: # Error if so utils.error('Section is null') else: # Now check if the current round is 0 if section.current_round == 0: # And redirect to an error if so self.redirect('/error?code=103') else: # Otherwise, we need to set our template values self.render_template(student, section)
def remove_student(self, section, email): """ Removes a specific students from the given section. Args: section (object): Section from which the student is to be removed. email (str): Email (ID) of the student to be removed. """ # First, grab the student from the db by the email passed in student = model.Student.get_by_id(email) # Check that there is actually a record for that email if not student: # And error if not utils.error('Student does not exist!', handler=self) else: # Create a new list for the section removing the student section.students = [s for s in section.students if s.email != email] # TODO better? use remove? # Check if the student is enrolled in this section if section.key in student.sections: # And remove them if so student.sections.remove(section.key) # end # And save both the student and section back to the db and log it student.put() section.put() utils.log( 'Student {0} has been removed from Section {1}'.format(str(student), str(section)), handler=self, type='Success!')
def start_rounds(self, instructor): # So first we need to get at the course and section course, section = utils.get_course_and_section_objs( self.request, instructor) # And grab all of the rounds for this section rounds = model.Round.query(ancestor=section.key).fetch() # The view requires at least a initial question to add rounds, but check if not rounds: # Send an error if no rounds exist for this section utils.error('Error! No initial question exists; cannot start yet.', handler=self) # And redirect return self.redirect('/') # end # Now simply turn on the first round section.current_round = 1 section.put() # Add the dummy read only round if it's a rounds based discussion if section.has_rounds: self.add_rounds(num_of_rounds=1, duration=0, instructor=instructor, type=model.Round.get_round_type('readonly'), buffer_bw_rounds=0, quiet=True) # And send a success message utils.log('Successfully started the first round.', type='Success!', handler=self)
def post(self): """ HTTP POST method to create groups. """ # First, check that the logged in user is an instructor instructor = utils.check_privilege(model.Role.instructor) if not instructor: # Send them home and short circuit all other logic return self.redirect('/') # end # So first we need to get at the course and section course, section = utils.get_course_and_section_objs(self.request, instructor) # Grab the action from the page action = self.request.get('action') # Check that the action was actually supplied if not action: # Error if not utils.error('Invalid argument: action is null', handler=self) else: # Switch on the action utils.log('action = ' + action) if action == 'add': # If add, grab the number of groups from the page group_count = int(self.request.get('groups')) # And modify the database self.modify_group_count(section, group_count) elif action == 'update': # For update, grab the group settings from the page groups = json.loads(self.request.get('groups')) # And modify the database self.update_groups(section, groups) else: # Send an error if a different action is supplied utils.error('Unknown action' + action if action else 'None', handler=self)
def add_section(self, course, section_name): """ Adds a section to the given course in the datastore. Args: course (object): Course to which the section is to be added. section_name (str): Name of the section; must be unique within the course. """ # First, start by grabbing the section passed in from the database section = model.Section.get_by_id(section_name, parent=course.key) # Double check that it doesn't already exist if section: # And send an error if it does utils.error(section_name + ' already exists', handler=self) else: # Otherwise, create it, save it to the database, and log it section = model.Section(parent=course.key, id=section_name) section.name = section_name section.put() utils.log(section_name + ' added', type='Success!') # TODO Include `Students from {{recent_section}} added to the {{current_section}}` - when it shows the 'Success' box after adding a section? if course.recent_section: recent_section = model.Section.get_by_id(course.recent_section, parent=course.key) for s in recent_section.students: section.students.append(s) student = model.Student.get_by_id(s.email) if section.key not in student.sections: student.sections.append(section.key) student.put() section.put()
def post(self): """ HTTP POST method to add the student. """ # First, check that the logged in user is an instructor instructor = utils.check_privilege(model.Role.instructor) if not instructor: # Send them home and short circuit all other logic return self.redirect('/') # end # So first we need to get at the course and section course, section = utils.get_course_and_section_objs(self.request, instructor) # And grab the action from the page action = self.request.get('action') # Check that the action was actually supplied if not action: # Error if not utils.error('Invalid arguments: course_name or section_name or actoin is null', handler=self) else: # Now switch on the action if action == 'add': # Grab a list of the emails from the page emails = json.loads(self.request.get('emails')) # And create new students from that list self.add_students(section, emails) elif action == 'remove': # Grab the email from the page to remove email = self.request.get('email').lower() # And remove it self.remove_student(section, email) else: # Send an error if any other action is supplied utils.error('Unexpected action: ' + action, handler=self)
def check_method(class_object, name, number_of_args): class_name = class_object.__name__ predicate = lambda x : inspect.ismethod(x) and x.__name__ == name matching_functions = inspect.getmembers(class_object, predicate) if len(matching_functions) != 1: utils.error("'{0}' class must have '{1}' method!".format(class_name, name)) spec = inspect.getargspec(matching_functions[0][1]) if len(spec.args) != number_of_args: utils.error("'{1}' method of '{0}' class must have exactly {2} argument{3}!". format(class_name, name, number_of_args, 's' if number_of_args > 1 else ''))
def toggle_anonymity(self, instructor): # So first we need to get at the course and section course, section = utils.get_course_and_section_objs( self.request, instructor) if section: section.is_anonymous = not section.is_anonymous section.put() utils.log('Anonymity toggled to: ' + str(section.is_anonymous)) else: utils.error('Section not found')
def toggle_round_structure(self, instructor): # So first we need to get at the course and section course, section = utils.get_course_and_section_objs( self.request, instructor) if section: section.has_rounds = not section.has_rounds section.put() utils.log('Rounds Structure toggled to: ' + str(section.has_rounds)) else: utils.error('Section not found')
def __check__(self, tests): tests = set(map(str, tests)) logging.info(len(tests)) compilation_tests = set(self.baselines["compile_time"].keys()) execution_tests = set(self.baselines["execution_time"].keys()) message = "Ethalon tests ({0}) differ from the ones for Wazuhl!" if tests != compilation_tests: utils.error(message.format("compilation")) if tests != execution_tests: utils.error(message.format("execution")) assert len(tests) > 0, "Test suite is empty!"
def add_rounds(self, num_of_rounds, duration, instructor, buffer_bw_rounds, type=2, quiet=False): # So first we need to get at the course and section course, section = utils.get_course_and_section_objs( self.request, instructor) # And grab all of the rounds for this section rounds = model.Round.query(ancestor=section.key).fetch() # The view requires at least a initial question to add rounds, but check if not rounds: # Send an error if no rounds exist for this section utils.error( 'Error! No initial question exists; cannot add new rounds yet.', handler=self) # And redirect return self.redirect('/') # end # We'll need this later on when doing the buffer stuff # if rounds_buffer < 0 # # Make sure the buffer value is positive # utils.error('Error! Buffer value must be greater than 0.', handler=self) # # And redirect # return self.redirect('/') # #end # Copy the summary round if it exists summary = copy_summary(section, rounds, num_of_rounds) # If it exists, pop it off the list if summary: rounds.pop() # end # Now create all the new rounds new_rounds = create_new_rounds(section, rounds, num_of_rounds, duration, buffer_bw_rounds, type) # And update the summary round update_summary(summary, new_rounds) # Now update the number of rounds attribute of the section update_section_rounds(new_rounds[-1].number, section) # Now send a success message if not quiet: utils.log( 'Successfully added {0} new rounds.'.format(num_of_rounds), type='Success!', handler=self) utils.log('Added Rounds = ' + str(new_rounds))
def check_method(class_object, name, number_of_args): class_name = class_object.__name__ class_fields = inspect.getmembers(class_object) matching_functions = [f for f in class_fields if f[0] == name] if len(matching_functions) != 1: utils.error("'{0}' class must have '{1}' method!".format( class_name, name)) spec = inspect.getargspec(matching_functions[0][1]) if len(spec.args) != number_of_args: utils.error( "'{1}' method of '{0}' class must have exactly {2} argument{3}!". format(class_name, name, number_of_args, 's' if number_of_args > 1 else ''))
def admin_panel(): if request.method == "POST": rcv = utils.fix_number(request.form.get('rcv')) print(rcv) keys = redis.lrange("keys", 0, -1) while True: key = utils.generate_random_key(4) if not key.encode() in keys: break if len(rcv) == config.phone_total_length: result = msg.send_sms("NiceSMS", rcv, f"Your one time key is: {key}") redis.lpush("keys", key) if isinstance(result, tuple): return result return render_template("result.html", msg=result, admin=True) elif len(rcv) == len(config.phone_prepend): print(f"Generated Anonymous Key | {key}") redis.lpush("keys", key) return jsonify({"key": key}) return utils.error("Invalid number!") return render_template("admin.html")
def add_instructor(self, email): """ Adds an instructor to the datastore. Args: email (str): Email of the instructor to be added. """ if email: _instructor = model.Instructor(id=email) _instructor.email = email _instructor.put() utils.log(email + ' has been added as an Instructor', type='Success!', handler=self) else: utils.error('Invalid arguments: email')
def _step(self, input, target, optimizer, epoch, n_batches, n_points, train_timer): start = time.time() input = Variable(input, requires_grad=False) target = Variable(target, requires_grad=False) if next(self.model.parameters()).is_cuda: input = input.cuda() target = target.cuda() if optimizer is not None: optimizer.zero_grad() output, kl = self.model(input) obj, main_obj, kl = self.criterion(output, target, self.model, kl, self.gamma_scheduler[epoch], n_batches, n_points) if optimizer is not None: obj.backward() if self.args.clip > 0: torch.nn.utils.clip_grad_value_(self.model.parameters(), self.args.clip) optimizer.step() error_metric, ece = utils.error(output, input, target, self.model, self.args) if train_timer: self.train_time += time.time() - start else: self.val_time += time.time() - start return error_metric.item(), obj.item(), main_obj.item(), kl.item(), ece
def get_msg(msg_id): try: return msg_client.message(msg_id) except messagebird.ErrorException: try: return msg_client.voice_message(msg_id) except messagebird.ErrorException: return utils.error("Message not found!")
def get_partial(self, percentage, part='top', rounding_method='roundx', include_unscored=False): ''' Get partial anime/manga list ''' # Anime/manga List Initiation entry_list = self.get_list(include_unscored=include_unscored) entry_list.sort(key=lambda i: i.my_score, reverse=True) # Anime/manga Count Calculation entry_count = percentage / 100 * len(entry_list) # Anime/manga Count Rounding Method if rounding_method == 'floor': entry_count = floor(entry_count) elif rounding_method == 'ceil': entry_count = ceil(entry_count) elif rounding_method == 'round': entry_count = round(entry_count) elif rounding_method == 'roundx': if entry_count % 0.5 == 0: entry_count = floor(entry_count) else: entry_count = round(entry_count) else: error('Invalid rounding_method `{}` of get_partial().'.format( rounding_method)) return None # Anime/manga List Slicing if part == 'top': return entry_list[:entry_count] elif part == 'bottom': entry_list.reverse() return entry_list[:entry_count] elif part == 'middle': middle = len(entry_list) // 2 upper = middle + floor(entry_count / 2) lower = middle - ceil(entry_count / 2) return entry_list[lower:upper] else: error('Invalid part `{}` of get_partial().'.format(part)) return None
def toggle_instructor(self, email): """ Toggles the status of the selected instructor between 'Active' and 'Inactive'. Args: email (str): Email (identifier) of the instructor to be added. """ if email: _instructor = model.Instructor.query(model.Instructor.email == email).get() if _instructor: _instructor.is_active = not _instructor.is_active _instructor.put() utils.log('Status changed for ' + email, handler=self) else: utils.error('Instructor (' + email + ') not found') else: utils.error('Invalid arguments: email')
def delete_round(self, instructor, round_id): # So first we need to get at the course and section course, section = utils.get_course_and_section_objs( self.request, instructor) # And grab all of the rounds for this section rounds = model.Round.query(ancestor=section.key).fetch() # The view requires at least a initial question to add rounds, but check if not rounds: # Send an error if no rounds exist for this section utils.error( 'Error! No initial question exists; cannot add new rounds yet.', handler=self) # And redirect return self.redirect('/') # end # Now shift all the rounds and remove the round with the input id shift_rounds(rounds, round_id) # Now let's check if we need to copy a summary question over or not if rounds[-1].description == 'summary': # Delete the next to last round and remove from the list rounds[-2].put().delete() rounds.pop(-2) # And copy the summary round and update the times summary = copy_summary(section, rounds, -1) # Remove the old summary from the list rounds.pop() update_summary(summary, rounds) else: # Finally, remove the very last round from the list rounds[-1].put().delete() # end # Since we shifted rounds forward, remove the last round from the list rounds.pop() # And update the section update_section_rounds(rounds[-1].number, section) # And send a success message utils.log('Successfully deleted round {0}'.format(round_id), type='Success!', handler=self)
def parse(): usage = "usage: %prog [options] [wazuhl installation directory]" version = "0.0.1a" parser = OptionParser(usage, version=version) parser.add_option('-o', '--output-directory', type='string', action='store', dest='output', default='train.out', help='Directory to store training files') (options, args) = parser.parse_args() if len(args) == 0: utils.error("Please, specify path to Wazuhl installation") installation = args[0] utils.check_file(installation) config.set_wd(installation) config.set_output(options.output)
def modify_group_count(self, section, group_count): """ Modifies the total number of groups in this section. Args: section (object): Section whose group count is to be modified group_count (int): The new total number of groups. """ # Double check that the passed in number of groups isn't null if not group_count: # Error if so utils.error('Groups count not available.', handler=self) else: if section.groups != group_count and group_count > 0: # If the total number of groups are not as requested change them section.groups = group_count section.put() # end utils.log('Groups modified.', type='Success!', handler=self)
def toggle_section(self, course, section_name): """ Toggles the status of a section between Active and Inactive. Args: course (object): Course under which this section exists section_name (str): Name of the section to be toggled. """ # First, start by grabbing the section from the passed in value _section = model.Section.get_by_id(section_name, parent=course.key) # Double check that it actually exists if _section: # Toggle the section to active, save it to the database, and log it _section.is_active = not _section.is_active _section.put() utils.log('Status changed for ' + section_name, type='Success!') else: # Send an error if the section passed in doesn't exist utils.error('Section ' + section_name + ' not found', handler=self)
def send_sms(src, dst, text, key=None): if key is not None: key = key.encode() keys = redis.lrange("keys", 0, -1) if key not in keys: return utils.error("Invalid key") fixed_src = utils.fix_number(src) if fixed_src.replace( config.phone_prepend, "").isdecimal() and len(fixed_src) == config.phone_total_length: src = fixed_src dst = utils.fix_number(dst) if len(dst) != config.phone_total_length: return utils.error("Destination number is not 8 characters long") if len(text) == 0 or (len(text.encode()) > 150 and key is not None): return utils.error("Invalid message length") if src.lower() == "nicesms" and key is not None: return utils.error("Reserved sender!") try: message = msg_client.message_create(src, dst, text) if key is not None: redis.lrem("keys", 0, key) except messagebird.ErrorException as e: print(e) return utils.error("Unknown Error! Contact the admin. (Key not used)") if key: print( f"Sent sms | {src} => {dst} | Key: {key.decode()} | Text: {text}") else: print(f"Sent sms | {src} => {dst} | Text: {text}") return message
def make_call(src, dst, text, key=None): if key is not None: key = key.encode() keys = redis.lrange("keys", 0, -1) if key not in keys: return utils.error("Invalid key") src = utils.fix_number(src) dst = utils.fix_number(dst) if len(dst) != 11 or len(src) != 11: return utils.error( "Destination and Source numbers are not 8 characters long") if len(text) == 0 or len(text.encode()) > 140: return utils.error("Invalid tts message length") try: result = msg_client.voice_message_create(dst, text, params={ "originator": src, "language": "da-DK" }) if key is not None: redis.lrem("keys", 0, key) except messagebird.ErrorException: return utils.error("Unknown Error! Contact the admin. (Key not used)") if key: print( f"Made call | {src} => {dst} | Key: {key.decode()} | Text: {text}") else: print(f"Made call | {src} => {dst} | Text: {text}") return result
def post(self): """ HTTP POST method to add a section to a course. """ # First, check that the logged in user is an instructor instructor = utils.check_privilege(model.Role.instructor) if not instructor: # Send them home and short circuit all other logic return self.redirect('/') # end # Otherwise, grab the course, section, and action from the webpage course_name = self.request.get('course') section_name = self.request.get('section') action = self.request.get('action') # Double check that all three were supplied if not course_name or not section_name or not action: # Error if not utils.error( 'Invalid arguments: course_name or section_name or action is null', handler=self) else: # Otherwise, grab the course from the database course = model.Course.get_by_id(course_name.upper(), parent=instructor.key) # And check that it exists and is active if not course or not course.is_active: # Error if not utils.error(course_name + ' does not exist OR is not active!', handler=self) else: # Otherwise, switch on the action if action == 'add': # Add a new section if action is add self.add_section(course, section_name.upper()) elif action == 'toggle': # Or toggle self.toggle_section(course, section_name.upper()) else: # Error if the action is neither toggle or add utils.error('Unexpected action:' + action, handler=self)
def get_grouped_list(self, include_unscored=False, group_by='series_type', sort_method='most_common', sort_order='descending', manual_sort=None, disassemble_key=None): ''' Get grouped anime/manga list ''' grouped_entry_list = dict() categories = list() filtered_entry_list = self.get_list(include_unscored=include_unscored) # Category Retrieval for _ in filtered_entry_list: if eval('_.{}'.format(group_by)) not in categories: categories.append(eval('_.{}'.format(group_by))) # Category Sorting if sort_method == 'most_common': categories.sort( key=lambda i: [eval('j.{}'.format(group_by)) for j in filtered_entry_list].count(i), reverse=sort_order != 'ascending') elif sort_method == 'alphabetical': categories.sort(reverse=sort_order != 'ascending') else: error('Invalid sort_method `{}` of get_grouped_list().'.format( sort_method)) return None # Manual Sort Override if manual_sort != None: old_categories = [i for i in categories] categories = list() for i in manual_sort: if i in old_categories: categories.append(i) old_categories.remove(i) categories += old_categories # Packing Categories for i in categories: grouped_entry_list[i] = [ j for j in filtered_entry_list if eval('j.{}'.format(group_by)) == i ] # Desired Data Retrieval if disassemble_key != None: for i in grouped_entry_list: for j in range(len(grouped_entry_list[i])): temp = [ 'grouped_entry_list[i][j].{}'.format(k) for k in disassemble_key ] for k in range(len(temp)): temp[k] = eval(temp[k]) grouped_entry_list[i][j] = temp # Return return grouped_entry_list
def save_submission(self, student, current_round): # Create a new response object response = model.Response(parent=current_round.key, id=student.email) # Start by grabbing data from the page option = self.request.get('option').lower() comment = self.request.get('comm') summary = self.request.get('summary') res = self.request.get('response') # Now check whether we're on a initial or summary or discussion round if current_round.is_quiz: # If it is, double check that they selected an answer and commented if current_round.quiz.options_total > 0 and not (option and comment): # if the question had 0 options, it's not an error # Error if not utils.error('Invalid Parameters: option or comment is null', handler=self) return # end # if current_round.number != 1 and not summary: # utils.error('Invalid Parameters: round is 1 or summary is null', handler=self) # return # And set the values in the response object response.option = option response.summary = summary if summary else '' else: # If a discussion question, grab the array of agree/disagree/neutral res = json.loads(res) # And double check that we have a comment and valid response if not (res and comment) or not utils.is_valid_response(res): # Error if not utils.error('Invalid Parameters: comment is null or res is not a valid response', handler=self) return # end # Now loop over the agree, etc. responses for i in range(1, len(res)): # And save them in our response object for the db response.response.append(res[i]) # Get all responses of the previous round prev_responses = find_prev_round_responses(current_round) # Fetch thumbs from the view in_thumbs = json.loads(self.request.get('thumbs')) if in_thumbs and prev_responses: utils.log('in_thumbs = {0}'.format(str(in_thumbs))) # Add the thumbs info. to the previous round's responses for prev_resp in prev_responses: # _thumbs = [] # Added because there was an error when submitting R1 post on the server; # could not replicate on dev server # Error: There was some error submitting your response please try again later. # Exception: local variable '_thumbs' referenced before assignment if in_thumbs.has_key(prev_resp.student): _thumbs = prev_resp.thumbs.copy() prev_resp.add_to_thumbs(student.email, in_thumbs[prev_resp.student]) # utils.log('For' + prev_resp.comment + ', old thumbs = ' # + (str(_thumbs) if _thumbs else 'Empty') # + ', new thumbs = ' + str(prev_resp.thumbs)) # Removed this instead of adding _thumbs = [] at line 385 prev_resp.put() # Grab the deadline and the current time deadline = current_round.deadline current_time = datetime.datetime.now() # And double check that they've submitted before the deadline ended if deadline >= current_time: # Set the comment and email, and save in the database response.comment = comment response.student = student.email # utils.log('comment = {0}, response = {1}'.format(comment, response.response)) response.put() utils.log('Your response has been saved. You can edit it any time before the deadline. ', type='Success!', handler=self) else: # Otherwise alert them that time has passed to submit for this round utils.error('Sorry, the time for submission for this round has expired \ and your response was not saved, please wait for the next round.', handler=self)
def post(self): """ HTTP POST method to submit the response. """ # First, check that the logged in user is a student student = utils.check_privilege(model.Role.student) if not student: # Redirect home if not a student return self.redirect('/home') # end # First, grab the section key from the page section_key = self.request.get('section') # Double check that we actually got a section key if section_key: try: # Grab the section from the database section = ndb.Key(urlsafe=section_key).get() # And double check that it's valid if section: # Grab the current round from the database current_round = model.Round.get_by_id(section.current_round, parent=section.key) # And double check that it's valid if current_round: # If this is a quiz round or if this section has roudns based discussions, # save it in the usual way if section.has_rounds or current_round.is_quiz: self.save_submission(student, current_round) else: # Otherwise, save as a Seq Discussion # 1. Make sure the author email passed from view is same as current student's email author_email = student.email student_info = utils.get_student_info(author_email, section.students) group_id = student_info.group group = model.Group.get_by_id(id=group_id, parent=section.key) # 2. create the post in that group if group: post = model.SeqResponse(parent=group.key) post.author = author_email post.author_alias = student_info.alias post.timestamp = str(datetime.datetime.now()) post.text = self.request.get('text') post.index = group.num_seq_responses + 1 post.put() group.num_seq_responses += 1 group.put() utils.log('Post created:' + str(post)) else: utils.error('Group is null') else: utils.error('Sorry! The round is not visible, please try again later.', handler=self) else: # section is null utils.error('Section is null', handler=self) except Exception as e: utils.error( 'Sorry! There was some error submitting your response please try again later. Exception: ' + e.message, handler=self) # TODO: use exceptions in all classes? else: utils.error('Invalid Parameters: section_key is null', handler=self)
def check_name(suite): if not hasattr(suite, "name"): utils.error( "Suite should have a name! Please, define a member variable in class" )
def update_groups(self, section, groups): """ Updates the groups assignments for the given section. Args: section (object): Section whose group assignments are to be updated. groups (dict): Dictionary of type ``{email:n}``, where ``email`` is the identifier for a student and ``n`` is the group-id that student is to be assigned to. """ # Double check that the passed in groups is non-null # for keys, values in groups.items(): # print(keys) # print(values) # print("--------------") if not groups: # Error if so utils.error('Groups information not available.', handler=self) else: # Loop over the students in the passed in section for student in section.students: # Check if the current student's email is in the groups if student.email in groups: # Set the student's group number to the index of the group student.group = int(groups[student.email]) # And then grab that group model from the database group = model.Group.get_by_id(student.group, parent=section.key) # -------------------Fix group allocation bug group_id = 1 while (group_id <= section.groups): pre_group = model.Group.get_by_id(group_id, parent=section.key) if not pre_group: group_id += 1 continue if student.email in pre_group.members: break group_id += 1 if group_id <= section.groups and group_id != student.group: pre_group.members.remove(student.email) pre_group.size = len(pre_group.members) pre_group.put() # -------------------Fix group allocation bug # Double check that it actually exists if not group: # And create it if not, giving it the proper number group = model.Group(parent=section.key, id=student.group) group.number = student.group # end # Now check if the student is listed in the correct group if student.email not in group.members: # If not, add that student in to the group group.members.append(student.email) # Update the size group.size = len(group.members) # Set the student's alias for that group student.alias = 'S' + str(group.size) # And commit the changes to the group group.put() # end # end # end # Commit the changes to the section and log it section.put() utils.log('Groups updated.', handler=self)