djangoone-to-onephotologue

FieldDoesNotExist with OneToOne


I want to make gallery for users based on Photologue app. In order to connect users' profiles to photologue's models I want to use OneToOne. Also I want to ... lets say 'override' photologue's get_absolute_url, which is used by templates.

# models
from photologue.models import Gallery
from profiles.models import UserProfile

class GalleryExtended(models.Model):
    gallery = models.OneToOneField(Gallery)
    user = models.ForeignKey(UserProfile, verbose_name=_('user'), on_delete=models.CASCADE)

    def get_absolute_url(self):
        return reverse('profiles_user:profiles_gallery-details', args=[self.user.user_url, self.gallery.slug])

# views
from photologue.views import Gallery
from profiles.models import UserProfile
from .models import GalleryExtended, PhotoExtended

def get_user_gallery_queryset(self):
    user = get_object_or_404(UserProfile, user_url=self.kwargs['user_url'])
    gallery = Gallery.objects.filter(galleryextended__user=user)
    return gallery

class ProfileGalleryDateView(object):
    date_field = 'date_added'
    allow_empty = True

    get_queryset = get_user_gallery_queryset

# site.com/username/gallery (shows photos + images with a filter by year)
class ProfileGalleryPhotoArchiveIndexView(ProfileGalleryDateView, ArchiveIndexView):
    template_name = 'galleries/gallery_n_photo_archive.html'

So in view if I do

gallery = Gallery.objects.filter(galleryextended__user=user)

templates start to use Photologue's get_absolute_url (I do not use the corephotologues url url(r'^photologue/', include('photologue.urls', namespace='photologue')), as I integrate the app in my own url schema)

Is it possible to revert to something like this

gallery = GalleryExtended.objects.filter(user=user).***(get fields from Gallery)***

and avoid django.core.exceptions.FieldDoesNotExist: GalleryExtended has no field named 'date_added' in order to start using get_absolute_url from GalleryExtended?

I know, it can easily be solved by extending photologues model via inheritance, but I want to know if is it possible to use OneToOne? because in some sources I have read it is recommended to use 1to1 instead of inheritance.


Solution

  • So, I think I understand now what you want to do. And it's wrong and hacky and bad in each and every way.
    First of all - model inheritance is nothing to be afraid of, in fact it's exactly the opposite. The hustle you're experiencing now is a great example of what's in store if you decide not to follow one of OOP's most important principles. Arguments such as "I read something from somewhere" can't really convert me and I truly recommend that you use inheritance instead of 1to1 relations. Not to mention that using 1to1 relation instead of inheritance also creates an extra DB query every time you need to use the objects in the same place.

    Despite not recommending to carry on with this setup, I can give you some possible versions how to achieve sth similar to what you want. It's not perfect because it's not meant to be used this way.

    If we're on the same page now, you have a structure such as this:

    class Model1(models.Model):
        name = models.CharField(max_length=128)
        state = models.CharField(max_length=128)
    
        def some_method(self):
            return self.name
    
    
    class Model2(models.Model):
        first = models.OneToOneField(Model1, related_name='second')
        name = models.CharField(max_length=128)
    
        def some_method(self):
            return self.name + '2345678'
    

    You want to query out all the Model2s and call Model1's some_method() on them (correct if wrong).
    This can be achieved with a hacky solution when you replace the self argument with another model that fits the criteria.
    I.e.

    all_method_values = []
    for obj in Model2.objects.all():
        all_method_values.append(Model1.some_method(obj))
    

    So how to write this in a more pythonic way and still keep the 1to1 relation? Change the method in question to a static method with an argument and if you need to call it as a bound method of either models, make them call the same static method with respective arguments. But code says more than 1000 words:

    # In the model
    def get_absolute_url(self):
        return GalleryExtended.get_gallery_url(self.user.user_url, self.gallery.slug)
    
    @staticmethod
    def get_gallery_url(user_url, slug):
         return reverse('profiles_user:profiles_gallery-details', args=[self.user.user_url, self.gallery.slug])
    

    Now, instead of the ugliness shown above, you can call without ugly hacking:

    all_method_values = []
    for obj in Model2.objects.all():
        all_method_values.append(Model1.get_gallery_url(obj.user.user_url, obj.gallery.slug))
    

    For the record, I still recommend inheritance, but this works okay as well. Doing this small step removes the confusion of mixing two separate models' bound methods and clears up where the data is coming from.