James, you rock dude. lol On Thu, Jul 20, 2017 at 2:20 PM, James Schneider <[email protected]> wrote:
> > > On Thu, Jul 20, 2017 at 8:44 AM, <[email protected]> wrote: > >> Hey, I've been running into issues trying to figure out how a user could >> be able to access multiple versions of an app. Here is the following >> example: >> >> I have an app named ANNA. ANNA is used to build labels and these labels >> are dependent upon different standards that come out. For example, we have >> standard 1700, standard 1800, standard 1900. The DjangoProject website has >> a bottom corner button that allows you to go to different versions of the >> django docs. I would like to implement that into ANNA, however I've come >> across a number of problems. So ANNA has the following app structure: >> >> > Those buttons refer to different releases of the same application. They > really are just shortcuts to a folder path that points at the particular > release: > > https://docs.djangoproject.com/en/1.7/ > https://docs.djangoproject.com/en/1.11/ > https://docs.djangoproject.com/en/dev/ > > I haven't investigated, but I'm guessing that each of those URL paths map > back to a directory containing a full release of the Django docs at the > appropriate version. > > my_project >> - ANNA >> - build (development) >> - search (development) >> >> I have tried to implement this versioning by the following partition >> >> *PARTITION 1* >> >> my_project >> - ANNA >> - build (development) >> - search (development) >> - build1700 >> - search1700 >> - build1800 >> - search1800 >> - build1900 >> - search1900 >> >> Unfortunately, the models used in all versions of build are the same >> across the board, and Django's reverse lookup for these models is giving me >> issues. For instance if I have a label model in build, I will also have a >> label model in build1700, build1800, etc. So I have to change the model >> names for each version, which means I have to change all of the business >> code to be version specific -- and I really do not want to do that since >> our label standards are also in development and change quite rapidly (like >> every four months right now). The versions are also similar enough to >> where when a new one comes out, I would like to be able to pretty much copy >> and paste the last version's code into the new version's code and make my >> changes like so. >> >> > Has anyone ever had to do anything like this or have any ideas of how to >> go about it? >> > > Disclaimer: I'm obviously not familiar with your application or workflow, > so take this with a grain of salt. My assumption is that you are creating > output for something like shipping labels that have to follow particular > formatting standards. If not, clarity via a brief overview of what your app > does would be helpful. > > The abbreviated version of what I have below is "I highly doubt you need > separate apps, but rather a single app with a better model design to > support multiple label/build types." > > > It appears you are creating a separate application for each label type. In > doing so, I'm assuming that you are copying the code from another "version" > (presumably development) and then modifying the entire application > accordingly. From what I gather, the work being performed in each "version" > is very similar with only a few minor differences (standard number, label > size, address placement, font used, etc.). If that is the case, the > versioning scheme you've developed is very inefficient and difficult to > maintain. > > I would recommend that you examine the workflow processes from a much > broader view. For example, to print a label, you would need to know it's > total size, type (water resistant, semi-gloss, etc.), printable area, and > so on. Presumably all of your labels have common attributes like these > (don't think about specific values at this point). > > Gather all of those common attributes for handy reference. In addition, > generate a list of unique attributes specific to a label type (perhaps a > built-in RFID tag ID that only higher-end labels have or something like > that). > > Now the fun/tricky part. There are two design patterns I see to address > your design. > > First pattern: > > Generate an abstract model that contains all of the common attributes that > most or all of your labels will have. This could be things like standard > number, size, weight, etc. You would then create regular models that > inherit from your new abstract model. Each of these models would either > contain no new fields (if everything they need is contained in the abstract > model), or would add the label-specific fields in. Things like the standard > number can be made available via class-level attributes (ie standard_number > = 1700) or via a common function defined in the abstract model that each > regular model can override if necessary. Here's how I would handle the > standard number: > > class AbstractLabel(models.Model): > > _standard_number = None > > size_horizontal = models.FloatField... > size_vertical = models.FloatField... > ....other fields > > def get_standard_number(self): > return getattr(self, '_standard_number', None) > > # You can also use a property > @property > def standard_number(self): > return getattr(self, '_standard_number', None) > > @standard_number.setter > def standard_number(self, value): > raise ValueError('You should not be setting this dynamically!') > > @standard_number.deleter > def standard_number(self): > raise ValueError('You should not be deleting this dynamically!') > > class Meta: > abstract = True > > class Label1700(AbstractLabel): > _standard_number = 1700 > > class Label1800(AbstractLabel): > _standard_number = 1800 > > class LabelBlah(AbstractLabel): > def get_standard_number(self): > return "I'm a blah label." > > This would generate a separate table for each label type, and provide a > common function between them to retrieve the standard number if you need it: > > >>> my_label = Label1700.objects.get() > >>> print(my_label.get_standard_number()) > 1700 > > You can add other methods to your label models that perform tasks (the > models are usually where business logic belongs) such as adding an address, > displaying the label, or generating and returning a PDF of the label based > on the current label data. I would highly recommend you come up with a > standard API for your label models. For example, all of your label models > should support the get_standard_number() method, so it doesn't matter which > model type you are working with, you can always get the model number the > same way. > > Where this pattern begins to fall apart is determining when to reference > each type of label. You'll likely need code to determine what type of label > you need, and then call that class dynamically. > > >>> standard = 1900 > >>> import importlib > >>> LabelModel = getattr(importlib.import_module("module.submodule"), > "Label{}".format(standard)) > >>> label_instance = LabelModel().objects.get() > >>> print(label_instance.get_standard_number()) > 1900 > > It's not the worst thing in the world to abstract away the actual model > class you are working with, and helps ensure that the business logic > methods stay generic and work regardless of the standard number. You can > also create generic methods for generating HTML, etc. that can be used in > templates, so that you can use a single template with any of your label > models. Granted, this overlaps your business logic with design, but is > usually acceptable when used sparingly and with a high-degree of > abstraction. > > Pros: Each model class has its own table, and is easier to query for if > you know what specific model type you are looking for. Business logic is > either abstracted away in the abstract parent model, or can be customized > for each individual label type directly in the concrete model class. > > Drawbacks: System-wide reporting is often an issue. A query for 'all' of > the labels in the system requires that you know all of the label standards > ahead of time, and query each model class individually. > > > Second pattern: > > Create a model that contains all of the possible attributes for all > labels. This provides a single monolithic Label() class to work with. Some > standard numbers may only use some of the attributes, so make sure to have > sane defaults for the more esoteric attributes. > > class Label(AbstractLabel): > pass > > The magic for this pattern is the split between the model class (which > contains the values for your model like standard number, etc.) and the > business logic. You would write a series of functions (or a separate class, > or series of classes) that would handle the business logic for your single > Label() model. In addition, the standard number in Label() would be stored > in the database, rather than held as a class-level attribute. For example: > > def get_label_depth(label): > if label.label_type != '3d': > # Our label is not 3-dimensional, so it has no depth... > return None > > return label.depth > > >>> my_label = Label.objects.get(standard_number=1700) > >>> my_label.standard_number > 1700 > >>> get_label_depth(my_label) > None > > The function should be able to handle any Label() object, regardless of > the standard number, etc. > > > > My personal preference would be the first design pattern. > > Phew, this is longer than I anticipated, almost there... > > > Now, to address the versioning, once we've abstracted away the label types > using one of the two patterns above, we now need to determine which label > type a user will use. The very simple answer is to either create a custom > user object and add the label type as an attribute directly to the user, or > create user profiles and have the label type as one of the attributes of > the user profile. > > In your case, it sounds like particular users will always have a > particular label type assigned, and that the label type will be needed for > most, if not all, requests. This would make it a good candidate for an > attribute directly on a custom user object. How you use it will depend on > the design pattern, but you'll have the necessary information to act > accordingly to whatever label type the user needs. You'll have a single > code base, and will be postured to easily handle more label types if they > become necessary, and updates to label standards can be implemented with a > small amount of work in most cases. > > You may run in to trouble with old labels if you need to keep them in > their original version, but the standard changes for new labels going > forward. > > >> >> Also, below is my current callback. >> >> >> Unhandled exception in thread started by <function wrapper at 0x38639b0> >>> Traceback (most recent call last): >>> File "/usr/lib64/python2.7/site-packages/django/utils/autoreload.py", >>> line 227, in wrapper >>> fn(*args, **kwargs) >>> File >>> "/usr/lib64/python2.7/site-packages/django/core/management/commands/runserver.py", >>> line 125, in inner_run >>> self.check(display_num_errors=True) >>> File "/usr/lib64/python2.7/site-packages/django/core/management/base.py", >>> line 405, in check >>> raise SystemCheckError(msg) >>> django.core.management.base.SystemCheckError: SystemCheckError: System >>> check identified some issues: >>> >>> ERRORS: >>> build1700.Bundle.user: (fields.E304) Reverse accessor for 'Bundle.user' >>> clashes with reverse accessor for 'Bundle.user'. >>> HINT: Add or change a related_name argument to the definition for >>> 'Bundle.user' or 'Bundle.user'. >>> build.Bundle.user: (fields.E304) Reverse accessor for 'Bundle.user' >>> clashes with reverse accessor for 'Bundle.user'. >>> HINT: Add or change a related_name argument to the definition for >>> 'Bundle.user' or 'Bundle.user'. >>> >>> > This is an issue with a model M2M relation. Sounds like you need to define > a unique related_name attribute. > > > > I'll shut up now. ;-) > > -James > > -- > You received this message because you are subscribed to the Google Groups > "Django users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/django-users. > To view this discussion on the web visit https://groups.google.com/d/ > msgid/django-users/CA%2Be%2BciU%3Dukwrm0Fre%3DyW0qSACoWdYJcDq8rN9CZQ9P9Ypu > aPZA%40mail.gmail.com > <https://groups.google.com/d/msgid/django-users/CA%2Be%2BciU%3Dukwrm0Fre%3DyW0qSACoWdYJcDq8rN9CZQ9P9YpuaPZA%40mail.gmail.com?utm_medium=email&utm_source=footer> > . > > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Django users" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/django-users. To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CAAuoY6NVH83-yRL5g7JNgNSCN3_6rjzAfcS3foWriNMPUoCvsQ%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.

