Overriding apps/dashboard/catalogue

2,186 views
Skip to first unread message

Chris L

unread,
Dec 24, 2013, 4:33:12 PM12/24/13
to django...@googlegroups.com
In my store, I have multiple companies that each have their own products. I need to create an entry into a related table each time a company creates a product so that I can track which products are associated with which companies. My first pass at this was to override dashboard/catalogue so that when create or update view gets called, I can just do this there.

So what I have done is created a local dashboard/ with a copy of app.py, views.py and __init__.py
in dasshboard/app.py I have changed it to:
from dashboard import views

then I created dashboard/catalogue/ with a copy of app.py, views.py and __init__.py
in dashboard/catalog/ap.py I have changed it to:
from dashboard.catalogue import views

then in my settings.py I have added to INSTALLED_APPS:
'dashboard', 
'dashboard.catalogue',

Now, I have some print statements to see what's happening. django seems to be using the local dashboard/ override, but in dashboard/app.py in class DashboardApplication,
    catalogue_app = get_class('dashboard.catalogue.app', 'application')
this seems to be resolving to the oscar version, not my local version.


When I access the dashboard everything works except clicking 'Catalogue > Products' which gives me the following error:

NoReverseMatch at /dashboard/catalogue/

u'catalogue' is not a registered namespace
Here is a copy of the back trace: http://dpaste.com/1521706/
I am pretty new to django and oscar. Wondering if I am going about approaching this with the correct solution? What have I done wrong?
Thanks guys!

Chris L

unread,
Dec 24, 2013, 10:43:51 PM12/24/13
to django...@googlegroups.com
Alright, I think I've figured it out, and I suspect this is a bug in Oscar.

In my setting.py with INSTALLED_APPS I have:
+ get_core_apps(['dashboard', 'dashboard.catalogue'])

Now, get_classes(module_label, classnames) in loading.py is incorrectly resolving these local apps and defaulting to oscar apps instead. In the following If statement:
    if '.' in app_module_path:
        base_package = app_module_path.rsplit('.' + app_label, 1)[0]
        local_app = "%s.%s" % (base_package, module_label)


As far as I can see the line should be:
        local_app = module_label

And this works correctly for me. Any ideas why this is the case? Should I file this bug?

Maik Hoepfel

unread,
Dec 25, 2013, 4:28:05 AM12/25/13
to django...@googlegroups.com
Hi Chris,

you definitely should be able to override all Oscar apps. I've forwarded
your email to Izidor who recently deeply dug into the loading part of Oscar.

As it's Christmas holidays in the UK, a response might have to wait
until they're back.

Cheers,

Maik
> Here is a copy of the back trace:http://dpaste.com/1521706/ <http://dpaste.com/1521706/>
>
> I am pretty new to django and oscar. Wondering if I am going about approaching this with the correct solution? What have I done wrong?
>
> Thanks guys!
>
> --
> https://github.com/tangentlabs/django-oscar
> http://django-oscar.readthedocs.org/en/latest/
> https://twitter.com/django_oscar
> ---
> You received this message because you are subscribed to the Google
> Groups "django-oscar" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to django-oscar...@googlegroups.com.
> Visit this group at http://groups.google.com/group/django-oscar.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-oscar/7518885e-3f09-4aa1-92b3-1189efa898b3%40googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

Chris L

unread,
Jan 28, 2014, 5:30:20 AM1/28/14
to django...@googlegroups.com
Any one look into this yet?

Just to reiterate the problem, I am unable to load my overloaded dashboard.partners app, it just keeps defaulting to the oscar version.

I believe the error may be in the get_classes() function in /oscar/core/loading.py but I am unsure of why the autor is parsing the module path and then attempting to put it back together. The intentions are not clear from the code.

It looks like there is an assumption here though that breaks down when we have a path with more than 1 '.'

Andrey Martyanov

unread,
Jan 29, 2014, 4:24:15 AM1/29/14
to django...@googlegroups.com
I also had problem overriding dashboard.catalogue app. I think there is a possible collision between apps.catalogue and apps.dashboard.catalogue when you use get_model, see https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/customer/history.py#L9, so when your access product details page you get "NoneType' object has no attribute 'browsable'" exception. See https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/customer/history.py#L20, Product in this context is None. I didn't have enough time to investigate the issue, but I've fixed that by explicitly overriding catalogue_app in DashboardApplication.

Izidor Matušov

unread,
Jan 29, 2014, 4:46:00 AM1/29/14
to django...@googlegroups.com
On 28/01/14 10:30, Chris L wrote:
> I believe the error may be in the get_classes() function in
> /oscar/core/loading.py but I am unsure of why the autor is parsing the
> module path and then attempting to put it back together. The intentions
> are not clear from the code.

Sorry for the long delay :( get_classes() is quite complicated in
comparision with other Oscar code as it does import during runtime.

What is the name of your dashboard.parent app? Can you provide the
traceback again?

Izidor

Jochen Wagner

unread,
Feb 1, 2014, 12:46:37 PM2/1/14
to django...@googlegroups.com, izidor....@tangentsnowball.com
I have the same problem here with overriding dashboard.orders. It keeps loading oscar.apps.dashboard.orders .

The debugger gives me the following outputs for get_classes, line 96, before trying to import local_app:

app_label = 'dashboard'
app_module_path = 'dashboard.orders'
base_package = 'dashboard.orders'
classnames = ['application']
local_app = 'dashboard.orders.dashboard.orders.app'
module_label = 'dashboard.orders.app

Jochen
Message has been deleted
Message has been deleted
Message has been deleted

goh chi yuan

unread,
Feb 5, 2014, 2:28:17 AM2/5/14
to django...@googlegroups.com
Same issue as Jochen here.

Jochen Wagner

unread,
Feb 5, 2014, 4:34:16 AM2/5/14
to django...@googlegroups.com

Maik Hoepfel

unread,
Feb 6, 2014, 2:06:02 PM2/6/14
to django...@googlegroups.com
Thanks for reporting this. The issue you've been seeing is that so far,
Oscar expected you to organise your local apps like
'yourshop.dashboard.orders' instead of 'dashboard.orders', which I think
is what both of you have done.

Izidor and I have decided to refactor get_classes to make it more
obvious what's going on. That also should fix your issue.

https://github.com/tangentlabs/django-oscar/commit/bfa79ea55289dc1b6a268bd3578ab14e79bdc3e6

Hope that helps,

Maik
> --
> https://github.com/tangentlabs/django-oscar
> http://django-oscar.readthedocs.org/en/latest/
> https://twitter.com/django_oscar
> ---
> You received this message because you are subscribed to the Google
> Groups "django-oscar" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to django-oscar...@googlegroups.com.
> Visit this group at http://groups.google.com/group/django-oscar.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-oscar/2150f216-e36c-4790-a989-b215e740dcea%40googlegroups.com.

Emanuele Bertoldi

unread,
Mar 14, 2014, 10:35:50 AM3/14/14
to django...@googlegroups.com
This didn't help me, nor the proposed workaround.

My solution was simply to add an empty models.py file to my custom dashboard module.

A note on docs should be added...

Dmitry Voronin

unread,
Mar 30, 2014, 9:59:40 AM3/30/14
to django...@googlegroups.com
Same problem with  
'NoneType' object has no attribute 'browsable'
Adding models.py to 'my_project/dashboard/catalogue' solved problem.

shafiqu...@gmail.com

unread,
Oct 6, 2014, 10:57:20 PM10/6/14
to django...@googlegroups.com
Hello all,

I am unable to properly override dashboard.catalogue. I am running django-oscar 0.8-dev and django 1.6.7. Can anyone advise?

Here is what is in my oscaroverrides/dashboard/catalogue directory:

├── __init__.py
├── __init__.pyc
├── admin.py
├── admin.pyc
├── app.py
├── app.pyc
├── forms.py
├── forms.pyc
├── models.py
├── models.pyc
├── views.py
└── views.pyc

Here is what is in my base.py (settings) file:

INSTALLED_APPS += [
    'django.contrib.flatpages',
    'compressor',
] + get_core_apps(['oscaroverrides.partner',
                   'oscaroverrides.address',
                   'oscaroverrides.checkout',
                   'oscaroverrides.order',
                   'oscaroverrides.shipping',
                   'oscaroverrides.catalogue',
                   'oscaroverrides.dashboard.partners',
                   'oscaroverrides.dashboard.orders',
                   'oscaroverrides.dashboard.catalogue',
    ])

in oscaroverrides/dashboard/catalogue/app.py:


in oscaroverrides/dashboard/catalogue/models.py:

from oscar.apps.dashboard.catalogue.models import *

in oscaroverrides/dashboard/catalogue/views.py:

from oscar.apps.dashboard.catalogue.views import *

and in oscaroverrides/dashboard/catalogue/forms.py:

from oscar.apps.dashboard.catalogue.forms import *
from oscar.apps.dashboard.catalogue.forms import StockRecordForm as CoreStockRecordForm

class StockRecordForm(CoreStockRecordForm):

    def __init__(self, product_class, user, *args, **kwargs):
   
        # The user kwarg is not used by stock StockRecordForm. We pass it
        # anyway in case one wishes to customise the partner queryset
        self.user = user
        super(StockRecordForm, self).__init__(*args, **kwargs)

        # If not tracking stock, we hide the fields
        if not product_class.track_stock:
            del self.fields['num_in_stock']
            del self.fields['low_stock_threshold']
        else:
            self.fields['price_excl_tax'].required = True
            self.fields['num_in_stock'].required = True

        print("**********************************************************")
        print(self.fields)

    class Meta:
        model = StockRecord
        exclude = ('product', 'num_allocated', 'partner')  


If I put "assert False" in here, it breaks, so I know that Django sees this. But this file has no effect - oscar uses the class from oscar.core instead.

Would anyone have any suggestions? 

Thanks,

Maik Hoepfel

unread,
Oct 9, 2014, 6:15:44 AM10/9/14
to django...@googlegroups.com
Hi Shafique,


> and in oscaroverrides/dashboard/catalogue/forms.py:
>
> from oscar.apps.dashboard.catalogue.forms import *

^----- I think this might your problem. This will import StockRecordForm
as StockRecordForm, and I presume get_class picks that up instead of
your version. You should be able to delete that line.

Let me know how it goes.

Cheers, Maik

shafiqu...@gmail.com

unread,
Oct 9, 2014, 11:12:24 AM10/9/14
to django...@googlegroups.com
Hello Maik,

Many thanks for replying. Unfortunately, this suggestion did not work - I removed the import * line and replaced it with "from oscar.apps.partner.models import *" (so that my stockrecord form would have access to the model). 

Would it actually be better to override the partner StockRecord model instead? I am trying to make it so that fullfillment partners can see only their own stockrecords, and not alter the partner field - but they should be able to view the partner field. Any suggestions on how to do this in the partner model? Here is how I would think to start: I would override the StockRecord model as follows:

# oscaroverrides/partner/models.py

from django.db import models
from oscar.apps.partner.abstract_models import AbstractStockRecord
from oscar.apps.address.abstract_models import AbstractPartnerAddress
from django.conf import settings

class StockRecord(AbstractStockRecord):

    partner = models.ForeignKey('partner.Partner', verbose_name=_("Partner"), 
        related_name='stockrecords', choices=this.user.partners.all())

There are two problems with this:

1. I don't think it is possible to override fields in the Abstract model
2. The field does not have access to the instance of a class (can't do "this.user" in the field definition). 

So I think the best way to proceed is to 

(a) override the form to restrict the user to seeing only partners to which she is linked, and to prevent the user from editing the partner field , and 
(b) put a clean method in the overridden partner/stockrecord model to prevent the user from editing the partner field:

# oscaroverrides/partner/models.py
...
    def clean(self):
        self.partner in this.user.partners.all() # how to do this?

Would anyone be able to advise on how to do (a) and (b) above? Thanks!

shafiqu...@gmail.com

unread,
Oct 9, 2014, 10:52:33 PM10/9/14
to django...@googlegroups.com
Ok, I think I've "solved" this, but my so-called solution will require a Pull Request. Here it is:

Step 1. I had to change the get_classes method in loaders.py

def get_classes(module_label, classnames):
   
    ...

    # import from Oscar package (should succeed in most cases)
    # e.g. 'oscar.apps.dashboard.catalogue.forms'
    printif(module_label, "***********************************************")
    oscar_module_label = "oscar.apps.%s" % module_label
    if not (module_label.find("catalogue") >= 0 and module_label.find("dashboard") >= 0):
        oscar_module = _import_module(oscar_module_label, classnames)
    else:
        oscar_module = None
    
    # returns e.g. 'oscar.apps.dashboard.catalogue',
    # 'yourproject.apps.dashboard.catalogue' or 'dashboard.catalogue',
    # depending on what is set in INSTALLED_APPS
    installed_apps_entry, app_name = _find_installed_apps_entry(module_label)

    if installed_apps_entry.startswith('oscar.apps.'):
        # The entry is obviously an Oscar one, we don't import again
        oscar_module = _import_module(oscar_module_label, classnames)
        local_module = None
        # oscar_module = _import_module(oscar_module_label, classnames)

    else:
        ...

The reason for this change is that the get_classes method imports ALL oscar.app apps, even those that are overridden. But it seems that once you import a Form, you can't override it (at least I couldn't). 

Step 2. In the my oscarroverrides/dashboard/catalogue/forms.py file, I copied the entire oscar.apps.dashboard.catalogue.forms.py file, and changed the class StockRecordForm(forms.ModelForm) to make it do what I want.

I of course had to add oscaroverrides.dashboard.catalogue my get_core_apps() in settings, etc (the usual other stuff). 

Here is what my oscarroverrides/dashboard/catalogue/ directory contains:

├── __init__.py
├── forms.py
└── models.py

So my question now is this: is this solution worthy of a pull request, or is there a better way?

Thanks!

shafiqu...@gmail.com

unread,
Oct 9, 2014, 11:05:26 PM10/9/14
to django...@googlegroups.com
Oops, sorry, made a mistake. My directory actually contains:

├── __init__.py
├── app.py
├── forms.py
├── models.py
├── views.py
└── widgets.py

The extra files just import * from the corresponding core app file.

shafiqu...@gmail.com

unread,
Nov 2, 2014, 11:09:43 PM11/2/14
to django...@googlegroups.com
Hello David, Maik,

Here is the version of get_classes in loading.py that works for me (please see below). With this version, I am able to override dashboard.catalogue. Note that there is some redundancy here:

        local_module = _import_module(local_module_label, classnames)
        oscar_module = _import_module(oscar_module_label, classnames)
        local_module = _import_module(local_module_label, classnames)

But this is necessary, since without it I would have to override ALL the files in the overridden app (even if just overriding with: from ... import *). What do you think? May I issue a PR?

def get_classes(module_label, classnames):

...
    # import from Oscar package (should succeed in most cases)
    # e.g. 'oscar.apps.dashboard.catalogue.forms'
    
    oscar_module_label = "oscar.apps.%s" % module_label
    
    # returns e.g. 'oscar.apps.dashboard.catalogue',
    # 'yourproject.apps.dashboard.catalogue' or 'dashboard.catalogue',
    # depending on what is set in INSTALLED_APPS
    installed_apps_entry, app_name = _find_installed_apps_entry(module_label)

    if installed_apps_entry.startswith('oscar.apps.'):
        # The entry is obviously an Oscar one, we don't import again
        oscar_module = _import_module(oscar_module_label, classnames)
        local_module = None
    else:
        
        # Attempt to import the classes from the local module
        # e.g. 'yourproject.dashboard.catalogue.forms'
        sub_module = module_label.replace(app_name, '')
        local_module_label = installed_apps_entry + sub_module
        local_module = _import_module(local_module_label, classnames)
        oscar_module = _import_module(oscar_module_label, classnames)
        local_module = _import_module(local_module_label, classnames)

    if oscar_module is local_module is None:
        # This intentionally doesn't raise an ImportError, because ImportError
        # can get masked in complex circular import scenarios.
        raise ModuleNotFoundError(
            "The module with label '%s' could not be imported. This either"
            "means that it indeed does not exist, or you might have a problem"
            " with a circular import." % module_label
        )

    # return imported classes, giving preference to ones from the local package
    return _pluck_classes([local_module, oscar_module], classnames)

Joshua Wedekind

unread,
Nov 12, 2014, 12:05:54 AM11/12/14
to django...@googlegroups.com
Ok. I figured out the reason my attempts to modify 'dashboard.catalogue' failed.

You need to add more than just a blank __init__.py file to /dashboard/catalogue/ folder. Specifically, your __init__.py file needs to contain this:
default_app_config = (
   
'oscar.apps.dashboard.catalogue.config.CatalogueDashboardConfig')

I have never put anything in __init__.py before, so this came as a surprise to me. And it came via trial and error.

Ray Riga

unread,
Nov 17, 2014, 7:08:22 PM11/17/14
to django...@googlegroups.com
Another helpful note from you, Josh! Thanks! 

Following the 1.0 docs, I tried using the new `oscar_fork_app` management command, and it doesn't fully work for forking the Dashboard. So close though! It generates a config file with an improper app name for the Oscar dashboard app being imported. The forked app's `config.py` file that's generated by running the command `./manage.py oscar_fork_app dashboard apps` contains:

class DashboardDashboardConfig(config.DashboardDashboardConfig):
    name = 'apps.dashboard'

When running any other manage.py command after this, I would get the error:
 

.../apps/dashboard/config.py", line 4 

AttributeError: 'module' object has no attribute 'DashboardDashboardConfig'

 So then I manually changed the class declaration in `config.py` to this to get it working:

class DashboardDashboardConfig(config.DashboardConfig):
    name = 'apps.dashboard'

The code that generates these forked `config.py` files is here:


I saw another thread where David was saying that there was some hacking required to get the Dashboard apps working with oscar_fork_app. It looks to be in need of a slight tweak.

I'd work on the change myself but I have never done a GitHub pull request before! I'm embarrassed. I have a lot of Git experience with private company repos, but am new to contributing on GitHub. If someone would be willing to give me a quick walkthrough or provide a good link explaining the process, I'd be happy to have a go at fixing this myself. Would be good to fix this in 1.0.1!

Thanks

-Ray
Reply all
Reply to author
Forward
0 new messages