Quantcast
Channel: Peter of the Norse Thinks He’s Smarter than You
Viewing all articles
Browse latest Browse all 54

The merge function

$
0
0

The merge function is hard to figure out. Fortunately, there’s the the delete_view method already in ModelAdmin. Open it in another window and follow along.

Let’s start with a pseudo-code outline:

def delete_view(self, request, object_id, extra_content=None):
    obj = this view 
    check for permissions
    if request.POST:
        new_obj = get other obj from(request.POST)
        for r in related rows:
            r.artist = new_obj
        new_obj.attributes = obj.attributes
        new_obj.save()
        obj.delete()
        return HttpResponseRedirect(somewhere)
    return renderToResponse(my page, context)

The first line is simple enough. Just copy from the parent obj = self.get_object(request, object_id). We also might as well copy opts = self.model._meta since we use it so much.


Checking for permissions is more difficult. There’s the obvious if not self.has_delete_permission(request, obj): raise PermissionDenied we copy over, but what about the related models? We should check that we have all the necessary permissions to change them. The original delete_viewuses a separate function for that called get_deleted_objects. We can’t use it directly, but it’s so informative. It returns a list of all effected rows in the database and those that can’t be deleted. Look though it yourself sometime. I’m stealing get_all_related_objects.

    rels = opts.get_all_related_objects()
    rel_obj = []
    lacking_perms = []
    for r in rels:
        count = getattr(obj, r.get_accessor_name()).all().count()
        if count == 0:
            continue
        if count == 1:
            name = r.model._meta.verbose_name
        else:
            name = r.model._meta.verbose_name_plural

        if not request.user.has_perm(r.model._meta.app_label + '.' + r.model._meta.get_change_permission()):
            lacking_perms.append(r.name)
        rel_obj.append({'name': name, 'count': count})

get_all_related_objects returns a list of RelatedObjects. I know, I know. It should be obvious. However, the class is misnamed. It should be Relationship. Some of the methods are:

get_accessor_name()
Returns the string of the name of the RelatedManager. For example: "album_set". Use like getattr(obj, r.get_accessor_name()).
model
The related Model.
field
The ForeignKey field on the other model.
name
A string of the form "app:model".

At the end we get an array of problems and an array of things that will get changed. Some might be saying that since we know all of the related models we don’t need to go though all of this. To them I say: No. One of the reasons I want to move everything over to Django is to automate other features of the station. Like artist of the day. And so it needs some future-proofing.


Getting the new object has a very Django way: forms. Thankfully I planned ahead and wrote MagicArtistField.

class FindOtherArtistForm(forms.forms.Form):
    entry = forms.MagicArtistField()
</p>The only thing is we need to make sure we don’t return the same artist again. The best way would be to change the magic artist field so that it can’t do the current artist. That’s way too much work for now, so I’m just going to do if new_obj is obj: errors = "You can't merge something with itself."</p>

The actual reassignment is getattr(obj, r.get_accessor_name()).all().update(**{r.field.name : new_obj}). See the discussion of RelatedObjects above.</code>


Moving the attributes is straight forward, assuming you know what they’re for.

                if not new_obj.last_played or new_obj.last_played < obj.last_played:
                    new_obj.last_played = obj.last_played
                if not new_obj.url:
                    new_obj.url = obj.url
                new_obj.times_played += obj.times_played
                new_obj.info = "%s\n%s" % (new_obj.info, obj.info)

The rest is copied almost verbatim from delete_view. I present the whole thing here to fill in the gaps:

def delete_view(self, request, object_id, extra_content=None):
    opts = self.model._meta
    obj = self.get_object(request, object_id)
    
    if not self.has_delete_permission(request, obj):
        raise PermissionDenied
    
    if obj is None:
        raise Http404('%(name)s object with primary key %(key)s does not exist.' % {'name': unicode(opts.verbose_name), 'key': object_id})
    
    rels = opts.get_all_related_objects()
    rel_obj = []
    lacking_perms = []
    for r in rels:
        count = getattr(obj, r.get_accessor_name()).all().count()
        if count == 0:
            continue
        if count == 1:
            name = r.model._meta.verbose_name
        else:
            name = r.model._meta.verbose_name_plural
        # Per object perms not avalable yet.
        if not request.user.has_perm(r.model._meta.app_label + '.' + r.model._meta.get_change_permission()):
            lacking_perms.append(r.name)
        rel_obj.append({'name': name, 'count': count})
    
    errors = None
    
    if request.POST: 
        if lacking_perms:
            raise PermissionDenied
        
        form = FindOtherArtistForm(request.POST)
        
        if form.errors:
            errors = form.errors['entry']
        else:
            new_obj = form.cleaned_data['entry']
            if new_obj is obj:
                errors = "You can't merge something with itself."
            else:
                    
                # move everything over
                for r in rels:
                    getattr(obj, r.get_accessor_name()).all().update(**{r.field.name : new_obj})
                # copy album info. 
                new_obj.review = "%s\n%s" % (new_obj.review, obj.review)
                if new_obj.location == u"NIL":
                    new_obj.location = obj.location
                elif obj.location not in new_obj.location and obj.location != u"NIL":
                    new_obj.location += ", %s" % obj.location
                
                new_obj.save()
                
                self.log_deletion(request, obj, unicode(obj))
                obj.delete()
        
                self.message_user(request, 'The %(name)s "%(obj)s" was merged with "%(new)s" successfully.' % {'name': unicode(opts.verbose_name), 'obj': unicode(obj), 'new': unicode(new_obj)})
        
                return HttpResponseRedirect("../../")
    else:
        form = FindOtherArtistForm()
    
    context = {
        "title": "Choose the other artist.",
        "object_name": unicode(opts.verbose_name),
        "object": obj,
        "form": form,
        "error": errors,
        "media": unicode(form.media),
        "affected": rel_obj,
        "lacking_perms": lacking_perms,
        "opts": opts,
        "root_path": self.admin_site.root_path,
        "app_label": opts.app_label,
    }
    context.update(extra_content or {})
    context_instance = template.RequestContext(request, current_app=self.admin_site.name)
    return render_to_response("dj_pro/admin/merge_confirmation.html", context, context_instance=context_instance)

You might notice that I skipped discussion of the stuff I copied. You’ll also notice that this is for albums, not artists. The actual differences are obvious.


Viewing all articles
Browse latest Browse all 54

Trending Articles