def download_submission(request, submission, revision): create_hit(request, submission, extra_info="download") if submission.sub_type == 'snippet': response = HttpResponse(mimetype="application/x-python") fname = submission.slug.replace('-', '_') + '.py' response["Content-Disposition"] = "attachment; filename=%s" % fname source = Site.objects.get_current().domain + \ submission.get_absolute_url() response.write('# Source: ' + source + '\n\n' + revision.item_code) return response if submission.sub_type == 'package': zip_dir = os.path.join(settings.SPC['ZIP_staging'], 'download') ensuredir(zip_dir) response = HttpResponse(mimetype="attachment; application/zip") zip_name = '%s-%d-%d.zip' % (submission.slug, submission.id, revision.rev_id_human) response['Content-Disposition'] = 'filename=%s' % zip_name full_zip_file = os.path.join(zip_dir, zip_name) if not os.path.exists(full_zip_file): # Set the repo's state to the state when that particular revision # existed out = submission.fileset.checkout_revision(revision.hash_id) if out: logger.info('Checked out revision "%s" for rev.id=%d' % \ (revision.hash_id, revision.id)) else: logger.error('Could not checked out revision "%s" for ' 'rev.id=%d' % (revision.hash_id, revision.id)) return page_404_error(request, ('Could not create the ZIP ' 'file. This error has been ' 'reported.')) zip_f = zipfile.ZipFile(full_zip_file, "w", zipfile.ZIP_DEFLATED) src_dir = os.path.join(settings.SPC['storage_dir'], submission.fileset.repo_path) for path, dirs, files in os.walk(src_dir): for name in files: file_name = os.path.join(path, name) file_h = open(file_name, "r") zip_f.write(file_name, file_name.partition(src_dir)[2]) file_h.close() for file_h in zip_f.filelist: file_h.create_system = 0 zip_f.close() # Return the repo checkout back to the most recent revision out = submission.fileset.checkout_revision(submission.\ last_revision.hash_id) # Return the ZIP file zip_data = open(full_zip_file, "rb") response.write(zip_data.read()) zip_data.close() return response
def profile_page(request, slug=None, user_id=None): """ Shows the user's profile. """ try: if user_id: the_user = models.User.objects.get(id=user_id) if the_user.is_active: return redirect(profile_page, the_user.profile.slug) elif slug is None: the_user = request.user else: the_user = models.User.objects.get(profile__slug=slug) except ObjectDoesNotExist: return page_404_error(request, 'No profile for that user.') # Don't show the profile for inactive (unvalidated) users if not (the_user.is_active): return page_404_error(request, "That user's profile isn't available.") # Items created by this user. Use the ``all()`` function first, to prevent # unvalidated submissions from showing all_revs = Revision.objects.all().filter(created_by=the_user)\ .order_by('-date_created') if the_user == request.user: no_entries = 'You have not submitted any entries to SciPy Central.' else: no_entries = 'This user has not submitted any entries to SciPy Central.' permalink = settings.SPC['short_URL_root'] + 'user/' + str( the_user.id) + '/' create_hit(request, the_user) return render_to_response( 'person/profile.html', {}, context_instance=RequestContext( request, { 'theuser': the_user, 'permalink': permalink, 'entries': paginated_queryset(request, all_revs), 'no_entries_message': no_entries, }))
def validate_submission(request, code): """ Validate a submission (via an emailed link to the user). From BSD licensed code, James Bennett https://bitbucket.org/ubernostrum/django-registration/src/58eef8330b0f/registration/models.py """ SHA1_RE = re.compile('^[a-f0-9]{40}$') if SHA1_RE.search(code): try: rev = models.Revision.objects.get(validation_hash=code) except ObjectDoesNotExist: return page_404_error(request, ('That validation code was invalid' ' or used already.')) rev.is_displayed = True rev.validation_hash = None rev.save() return redirect(rev.get_absolute_url()) else: return page_404_error(request, ('That validation code is invalid.'))
def profile_page(request, slug=None, user_id=None): """ Shows the user's profile. """ try: if user_id: the_user = models.User.objects.get(id=user_id) if the_user.is_active: return redirect(profile_page, the_user.profile.slug) elif slug is None: the_user = request.user else: the_user = models.User.objects.get(profile__slug=slug) except ObjectDoesNotExist: return page_404_error(request, 'No profile for that user.') # Don't show the profile for inactive (unvalidated) users if not(the_user.is_active): return page_404_error(request, "That user's profile isn't available.") # Items created by this user. Use the ``all()`` function first, to prevent # unvalidated submissions from showing all_revs = Revision.objects.all().filter(created_by=the_user)\ .order_by('-date_created') if the_user == request.user: no_entries = 'You have not submitted any entries to SciPy Central.' else: no_entries = 'This user has not submitted any entries to SciPy Central.' permalink = settings.SPC['short_URL_root'] + 'user/' + str(the_user.id) + '/' create_hit(request, the_user) return render_to_response('person/profile.html', {}, context_instance=RequestContext(request, {'theuser': the_user, 'permalink': permalink, 'entries':paginated_queryset(request, all_revs), 'no_entries_message': no_entries, }))
def edit_submission(request, submission, revision): if submission.sub_type in ['link', 'package'] and \ request.user != submission.created_by: return page_404_error(request, ('You are not authorized to edit that ' 'submission. Only the original author ' 'may edit it.')) # if by POST, we are in the process of editing, so don't send the revision if request.POST: return new_or_edit_submission(request, bound_form=False) else: return new_or_edit_submission(request, bound_form=revision)
def profile_page_edit(request, slug): """ User wants to edit his/her profile page. """ # First verify that request.user is the same as slug if request.user.profile.slug != slug: return page_404_error(request, ('You are not authorized to edit that ' 'profile. Only that user may edit it.')) if request.POST: form = forms.ProfileEditForm(request.POST) if form.is_valid(): # Update profile information user = request.user previous_email = '' if form.cleaned_data['email'] != user.email: previous_email = user.email user.email = form.cleaned_data['email'] user.save() user.profile.affiliation = form.cleaned_data['affiliation'] user.profile.country = form.cleaned_data['country'] user.profile.bio = form.cleaned_data['bio'] user.profile.bio_html = compile_rest_to_html( form.cleaned_data['bio']) user.profile.uri = form.cleaned_data['uri'] user.profile.save() tag_list = get_and_create_tags(form.cleaned_data['interests']) # First delete all the user's previous interests, so we can update # them with the new ones for interest in models.InterestCreation.objects.filter(user=user): #.profile.interests.all(): interest.delete() # Add user's interests through the intermediate # model instance for tag in tag_list: tag_intermediate = models.InterestCreation(user=user.profile, tag=tag) tag_intermediate.save() logger.debug('User "%s" added interest "%s" to their profile'%\ (user.profile.slug, str(tag))) # Resave the user profile to capture their interests in the search # results. user.profile.save() if previous_email: ctx_dict = { 'new_email': user.email, 'admin_email': settings.DEFAULT_FROM_EMAIL, 'username': user.username } message = render_to_string(\ 'person/email_about_changed_email_address.txt', ctx_dict) send_email((previous_email, ), ("SciPy Central: change of " "email address"), message=message) return redirect(profile_page, user.profile.slug) else: user = request.user interests = ','.join( list(set([str(intr) for intr in user.profile.interests.all()]))) fields = { 'uri': user.profile.uri, 'email': user.email, 'bio': user.profile.bio, 'interests': interests, 'country': user.profile.country, 'affiliation': user.profile.affiliation, 'pk': user.id, } form = forms.ProfileEditForm(fields) return render_to_response('submission/new-item.html', {}, context_instance=RequestContext( request, { 'form': form, 'buttontext': 'Update your profile', 'pagetitle': 'Update your profile', }))
def decorator(request, item_id, rev_id=None, slug=None, filename=None): """Retrieves the ``Submission`` and ``Revision`` objects when given, at a minimum the submission's primary key (``item_id``). Since the submission can have more than 1 revision, we can get a specific revision, ``rev_id``, otherwise we will always get the latest revision. ``slug`` is ignored for now - just used to create good SEO URLs. """ try: # Use the Submissions manager's ``all()`` function the_submission = models.Submission.objects.all().filter(id=item_id) except ObjectDoesNotExist: return page_404_error(request, 'You request a non-existant item') if len(the_submission) == 0: return page_404_error(request, 'This item does not exist yet') the_submission = the_submission[0] the_revision = the_submission.last_revision if rev_id: # can be None or '': all_revisions = the_submission.revisions.all() rev_index = int(rev_id) - 1 if rev_index < 0: rev_index = len(all_revisions) - 1 try: the_revision = all_revisions[rev_index] except (ValueError, IndexError): return page_404_error(request, ('The requested revision is ' 'non-existant.')) # Don't return revisions that are not approved for display yet if not isinstance(the_revision, models.Revision) or\ not(the_revision.is_displayed): return page_404_error(request, "That revision isn't available yet.") # Is the URL of the form: "..../NN/MM/edit"; if so, then edit the item path_split = request.path.split('/') if len(path_split) > 4 and path_split[4] in ['download', 'show']: if path_split[4] == 'show' and len(path_split) >= 6: return view_function(request, the_submission, the_revision, filename=path_split[5:]) else: return view_function(request, the_submission, the_revision) # Is the URL not the canonical URL for the item? .... redirect the user else: if rev_id is None: rev_id_str = '0' do_redirect = True else: rev_id_str = str(the_revision.rev_id + 1) do_redirect = False if slug is None or the_revision.slug != slug or do_redirect: return redirect('/'.join( ['/item', item_id, rev_id_str, the_revision.slug]), permanent=True) return view_function(request, the_submission, the_revision)
def download_submission(request, submission, revision): create_hit(request, submission, extra_info="download") if submission.sub_type == 'snippet': response = HttpResponse(mimetype="application/x-python") fname = submission.slug.replace('-', '_') + '.py' response["Content-Disposition"] = "attachment; filename=%s" % fname source = Site.objects.get_current().domain + \ submission.get_absolute_url() response.write('# Source: ' + source + '\n\n' + revision.item_code) return response if submission.sub_type == 'package': # Checkout the repo to the revision version checkout = submission.fileset.checkout_revision(revision.hash_id) if not checkout: logger.error('Could not checkout revision %s for rev.id %d' % (revision.hash_id, revision.pk)) return page_404_error('Could not create ZIP file. This error has ' 'been reported') logger.info('Checkout revision %s for rev.id %d' % (revision.hash_id, revision.pk)) package_data = StringIO() zipf = zipfile.ZipFile(package_data, 'w', zipfile.ZIP_DEFLATED) full_repo_path = os.path.abspath( os.path.join(settings.SPC['storage_dir'], submission.fileset.repo_path)) for path, dirs, files in os.walk(full_repo_path): # ignore revision backend dir if os.path.split( path)[1] == '.' + settings.SPC['revisioning_backend']: for entry in dirs[:]: dirs.remove(entry) continue for name in files: full_name = os.path.abspath(os.path.join(path, name)) zipf.write(full_name, full_name.partition(full_repo_path)[2]) for name in zipf.filelist: name.create_system = 0 zipf.close() package_data.seek(0) response = HttpResponse(mimetype='attachment; application/zip') response['Content-Disposition'] = 'filename=%s-%d-%d.zip' \ % (submission.slug, submission.pk, revision.rev_id_human) response.write(package_data.read()) package_data.close() # checkout the repo to latest revision last_revision = submission.last_revision checkout = submission.fileset.checkout_revision(last_revision.hash_id) if not checkout: logger.error('Could not checkout out revision %s for rev.id %d' % (last_revision.hash_id, last_revision.pk)) return response
def decorator(request, item_id, rev_id=None, slug=None, filename=None): """Retrieves the ``Submission`` and ``Revision`` objects when given, at a minimum the submission's primary key (``item_id``). Since the submission can have more than 1 revision, we can get a specific revision, ``rev_id``, otherwise we will always get the latest revision. ``slug`` is ignored for now - just used to create good SEO URLs. """ try: # Use the Submissions manager's ``all()`` function the_submission = models.Submission.objects.all().filter(id=item_id) except ObjectDoesNotExist: return page_404_error(request, 'You request a non-existant item') if len(the_submission) == 0: return page_404_error(request, 'This item does not exist yet') the_submission = the_submission[0] the_revision = the_submission.last_revision if rev_id: # can be None or '': all_revisions = the_submission.revisions.all() rev_index = int(rev_id)-1 if rev_index < 0: rev_index = len(all_revisions)-1 try: the_revision = all_revisions[rev_index] except (ValueError, IndexError): return page_404_error(request, ('The requested revision is ' 'non-existant.')) # Don't return revisions that are not approved for display yet if not isinstance(the_revision, models.Revision) or\ not(the_revision.is_displayed): return page_404_error(request, "That revision isn't available yet.") # Is the URL of the form: "..../NN/MM/edit"; if so, then edit the item path_split = request.path.split('/') if len(path_split)>4 and path_split[4] in ['edit', 'download', 'show']: if path_split[4] == 'show' and len(path_split)>=6: return view_function(request, the_submission, the_revision, filename=path_split[5:]) else: return view_function(request, the_submission, the_revision) # Is the URL not the canonical URL for the item? .... redirect the user else: if rev_id is None: rev_id_str = '0' do_redirect = True else: rev_id_str = str(the_revision.rev_id+1) do_redirect = False if slug is None or the_revision.slug != slug or do_redirect: return redirect('/'.join(['/item', item_id, rev_id_str, the_revision.slug]), permanent=True) return view_function(request, the_submission, the_revision)
def download_submission(request, submission, revision): create_hit(request, submission, extra_info="download") if submission.sub_type == 'snippet': response = HttpResponse(mimetype="application/x-python") fname = submission.slug.replace('-', '_') + '.py' response["Content-Disposition"] = "attachment; filename=%s" % fname source = Site.objects.get_current().domain + \ submission.get_absolute_url() response.write('# Source: ' + source + '\n\n' + revision.item_code) return response if submission.sub_type == 'package': # Checkout the repo to the revision version checkout = submission.fileset.checkout_revision(revision.hash_id) if not checkout: logger.error('Could not checkout revision %s for rev.id %d' % (revision.hash_id, revision.pk)) return page_404_error('Could not create ZIP file. This error has ' 'been reported') logger.info('Checkout revision %s for rev.id %d' % (revision.hash_id, revision.pk)) package_data = StringIO() zipf = zipfile.ZipFile(package_data, 'w', zipfile.ZIP_DEFLATED) full_repo_path = os.path.abspath(os.path.join(settings.SPC['storage_dir'], submission.fileset.repo_path)) for path, dirs, files in os.walk(full_repo_path): # ignore revision backend dir if os.path.split(path)[1] == '.' + settings.SPC['revisioning_backend']: for entry in dirs[:]: dirs.remove(entry) continue for name in files: full_name = os.path.abspath(os.path.join(path, name)) zipf.write(full_name, full_name.partition(full_repo_path)[2]) for name in zipf.filelist: name.create_system = 0 zipf.close() package_data.seek(0) response = HttpResponse(mimetype='attachment; application/zip') response['Content-Disposition'] = 'filename=%s-%d-%d.zip' \ % (submission.slug, submission.pk, revision.rev_id_human) response.write(package_data.read()) package_data.close() # checkout the repo to latest revision last_revision = submission.last_revision checkout = submission.fileset.checkout_revision(last_revision.hash_id) if not checkout: logger.error('Could not checkout out revision %s for rev.id %d' % (last_revision.hash_id, last_revision.pk)) return response
def profile_page_edit(request, slug): """ User wants to edit his/her profile page. """ # First verify that request.user is the same as slug if request.user.profile.slug != slug: return page_404_error(request, ('You are not authorized to edit that ' 'profile. Only that user may edit it.')) if request.POST: form = forms.ProfileEditForm(request.POST) if form.is_valid(): # Update profile information user = request.user previous_email = '' if form.cleaned_data['email'] != user.email: previous_email = user.email user.email = form.cleaned_data['email'] user.save() user.profile.affiliation = form.cleaned_data['affiliation'] user.profile.country = form.cleaned_data['country'] user.profile.bio = form.cleaned_data['bio'] user.profile.bio_html = compile_rest_to_html(form.cleaned_data['bio']) user.profile.uri = form.cleaned_data['uri'] user.profile.save() tag_list = get_and_create_tags(form.cleaned_data['interests']) # First delete all the user's previous interests, so we can update # them with the new ones for interest in models.InterestCreation.objects.filter(user=user): #.profile.interests.all(): interest.delete() # Add user's interests through the intermediate # model instance for tag in tag_list: tag_intermediate = models.InterestCreation(user=user.profile, tag=tag) tag_intermediate.save() logger.debug('User "%s" added interest "%s" to their profile'%\ (user.profile.slug, str(tag))) # Resave the user profile to capture their interests in the search # results. user.profile.save() if previous_email: ctx_dict = {'new_email': user.email, 'admin_email': settings.DEFAULT_FROM_EMAIL, 'username': user.username} message = render_to_string(\ 'person/email_about_changed_email_address.txt', ctx_dict) send_email((previous_email,), ("SciPy Central: change of " "email address"), message=message) return redirect(profile_page, user.profile.slug) else: user = request.user interests = ','.join(list(set([str(intr) for intr in user.profile.interests.all()]))) fields = {'uri': user.profile.uri, 'email': user.email, 'bio': user.profile.bio, 'interests': interests, 'country': user.profile.country, 'affiliation': user.profile.affiliation, 'pk': user.id, } form = forms.ProfileEditForm(fields) return render_to_response('submission/new-item.html', {}, context_instance=RequestContext(request, {'form': form, 'buttontext': 'Update your profile', 'pagetitle': 'Update your profile', }))
def create_or_edit_submission_revision(request, item, is_displayed, user, commit=False, ): """ Creates a new ``Submission`` and ``Revision`` instance. Returns these in a tuple. """ # NOTE: the ``user`` will always be a valid entry in our database. Code # posted by users that have not yet validated themselves is not displayed # until they do so. if item.cleaned_data['pk']: # We are editing an existing submission: pull it from the DB try: sub = models.Submission.objects.get(id=item.cleaned_data['pk']) except ObjectDoesNotExist: logger.error('Submission was not found when requesting "%s"' %\ request.path) page_404_error(request, ('You requested an invalid submission to ' 'edit')) else: # A new submission sub = models.Submission.objects.create_without_commit(created_by=user, sub_type=item.cleaned_data['sub_type']) # Process any tags tag_list = get_and_create_tags(item.cleaned_data['sub_tags']) # Create a ``Revision`` instance. Must always have a ``title``, # ``created_by``, and ``description`` fields; the rest are set according # to the submission type, ``sub.sub_type`` hash_id = '' if sub.sub_type == 'link': sub_license = None item_url = item.cleaned_data['item_url'] item_code = None elif sub.sub_type == 'snippet': sub_license = item.cleaned_data['sub_license'] item_url = None item_code = item.cleaned_data['snippet_code'] elif sub.sub_type == 'package': sub_license = item.cleaned_data['sub_license'] item_url = None item_code = None # Handle the ZIP file more completely only when the user commits. # ZIP file has been validated: OK to save it to the server # However, this might be the second time around, so skip saving it # (happens after preview, or if user resumes editing submission) if not hasattr(request.FILES['package_file'], 'skip_validation'): zip_f = models.ZipFile(raw_zip_file=request.FILES['package_file'], zip_hash=request.POST.get('package_hash', '')) zip_f.save() # Convert the raw ReST description to HTML using Sphinx: could include # math, paragraphs, <tt>, bold, italics, bullets, hyperlinks, etc. description_html = compile_rest_to_html(item.cleaned_data['description']) item_highlighted_code = highlight_code(item.cleaned_data.get(\ 'snippet_code', None)) rev = models.Revision.objects.create_without_commit( entry=sub, title=item.cleaned_data['title'], created_by=user, sub_license=sub_license, description=item.cleaned_data['description'], description_html=description_html, hash_id=hash_id, item_url=item_url, item_code=item_code, item_highlighted_code=item_highlighted_code, is_displayed=is_displayed, ) user_url = settings.SPC['short_URL_root'] + 'user/' + str(user.id) if commit: # Save the submission, then the revision. If we have a primary key # (case happens when user is editing a previous submission), then # do not save the submission (because the Submission object has fields # that will never change once it has been first created). Only set # the ``pk`` so that the new revision object is correct. if item.cleaned_data['pk']: # We are updating an existing ``sub`` item (see code above) pass else: sub.save() rev.entry_id = sub.id if not is_displayed: rev.validation_hash = create_validation_code(rev) rev.save() # Storage location: if we do save files it will be here datenow = datetime.datetime.now() year, month = datenow.strftime('%Y'), datenow.strftime('%m') repo_path = os.path.join(year, month, '%06d'% sub.id) full_repo_path = os.path.join(settings.SPC['storage_dir'], repo_path) if sub.sub_type == 'package': # Save the uploaded file to the server. At this point we are sure # it's a valid ZIP file, has no malicious filenames, and can be # unpacked to the hard drive. See validation in ``forms.py``. if os.path.exists(full_repo_path) and sub.fileset.repo_path == \ repo_path: # Make a temporary directory and copy the existing package # repository to that location temp_dir = tempfile.mkdtemp(prefix='tmp_spc_') src = os.path.join(full_repo_path, '.' + \ settings.SPC['revisioning_backend']) shutil.move(src, temp_dir) shutil.rmtree(full_repo_path, ignore_errors=True) else: temp_dir = None # Create/ensure destination directory exists ensuredir(full_repo_path) # Copy ZIP file zip_file = request.FILES['package_file'] dst = os.path.join(full_repo_path, zip_file.name) src = os.path.join(settings.SPC['ZIP_staging'], zip_file.name) shutil.copyfile(src, dst) # os.remove(src) Keep the original ZIP file, for now # Remove the entry from the database zip_hash = request.POST.get('package_hash', '') zip_objs = models.ZipFile.objects.filter(zip_hash=zip_hash) if zip_objs: zip_objs[0].delete() # Unzip file and commit contents to the repo zip_f = zipfile.ZipFile(dst, 'r') zip_f.extractall(full_repo_path) zip_f.close() os.remove(dst) # but delete the copy # Delete common RCS directories that might have been in the ZIP for path, dirs, files in os.walk(full_repo_path): if os.path.split(path)[1] in settings.SPC['common_rcs_dirs']: shutil.rmtree(path, ignore_errors=True) if temp_dir: src = os.path.join(temp_dir, '.' + \ settings.SPC['revisioning_backend']) dst = os.path.join(full_repo_path , '.' + \ settings.SPC['revisioning_backend']) try: os.rename(src, dst) except os.error, e: # For cases when /tmp is on a different filesystem # (usually production servers) import errno if e.errno == errno.EXDEV: shutil.copytree(src, dst, symlinks=True) shutil.rmtree(src) else: raise shutil.rmtree(temp_dir, ignore_errors=True) repo = sub.fileset.get_repo() else: # Create the repo sub.fileset = FileSet.objects.create(repo_path=repo_path) repo = sub.fileset.create_empty() sub.save() # Then add all files from the ZIP file to the repo. Add directories # at a time rather than file-by-file. for path, dirs, files in os.walk(full_repo_path): if os.path.split(path)[1] == '.' + \ settings.SPC['revisioning_backend']: for entry in dirs[:]: dirs.remove(entry) continue all_files = [] for name in files: all_files.append(os.path.join(path, name)) if all_files: repo.add(patterns=all_files, ignore_errors=True) # Add "DESCRIPTION.txt" descrip_name = os.path.join(full_repo_path, 'DESCRIPTION.txt') descrip_file = file(descrip_name, 'w') descrip_file.write(rev.description) descrip_file.close() sub.fileset.add_file(descrip_name, user=user_url, commit_msg=('Added/updated files from web-uploaded ' 'ZIP file. Added DESCRIPTION.txt also.')) if sub.sub_type == 'snippet': fname = rev.slug.replace('-', '_') + '.py' if not item.cleaned_data['pk']: # Create a new repository for the files sub.fileset = FileSet.objects.create(repo_path=repo_path) sub.save() commit_msg = ('Add "%s" to the repo ' 'based on the web submission by user "%s"') %\ (fname, user_url) else: commit_msg = ('Update of file(s) in the repo ' 'based on the web submission by user "%s"') %\ (user_url) sub.fileset.add_file_from_string(fname, request.POST['snippet_code'], user=user_url, commit_msg=commit_msg) if sub.sub_type in ['snippet', 'package']: license_file = settings.SPC['license_filename'] license_text = get_license_text(rev) sub.fileset.add_file_from_string(license_file, license_text, user="******", commit_msg="SPC: added/updated license file" ) rev.hash_id = sub.fileset.get_hash() # Once you have the revision you can add tags through the intermediate # model instance (which tracks the user that added the tag and when). for tag in tag_list: tag_intermediate = models.TagCreation(created_by=user, revision=rev, tag=tag) tag_intermediate.save() logger.debug('User "%s" added tag "%s" to rev.id=%d' % ( user.profile.slug, str(tag), rev.id)) # Update the search index so that the tags are included in the search rev.save() # log the new submission and revision logger.info('New %s: %s [id=%d] and revision id=%d' % ( sub.sub_type, item.cleaned_data['title'], sub.id, rev.id))