Should objects know of the objects they're used in? - oop

class Item:
def __init__(self, box, description):
self._box = box
self._description = description
class Box:
def __init__(self):
self.item_1 = Item(self, 'A picture')
self.item_2 = Item(self, 'A pencil')
#etc
old_stuff = Box()
print(old_stuff.item_1.box.item_1.box.item_2.box.item_1)
Above is shown an example piece of code which demonstrates my problem better than I ever could with plain text. Is there a better way to find in what box something is? (In what box is the picture?) Since I am not particularly fond of the above solution because it allows for this weird up and down calling which could go on forever. Is there a better way to solve this problem or is this just a case of: If it's stupid and it works, it ain't stupid.
Note: this trick isn't python specific. It's doable in all object-oriented programming laguages.

There is no right or wrong way to do this. The solution depends on how you want to use the object.
If your use-case requires that an item know in which box it is stored, then you need a reference to the box; if not, then you don't need the association.
Similarly, if you need to which items are in a given box, then you need references to the items in the box object.
The immediate requirement (that is, the current context) always dictates how one designs a class model; for example, one models an item or a box differently in a UI layer from how one would model it in a service layer.

You must introduce new class - ItemManager or simply dict or other external structure to store information about which box contain your item:
class Item:
def __init__(self, description):
self.description = description
class Box:
def __init__(self, item_1, item_2):
self.item_1 = item_1
self.item_2 = item_2
class ItemManager:
def __init__(self):
self.item_boxes = {}
def register_item(self, item, box):
self.item_boxes[item] = box
def deregister_item(self, item):
del self.item_boxes[item]
def get_box(self, item):
return self.item_boxes.get(item, None)
item_manager = ItemManager()
item_1 = Item("A picture")
item_2 = Item("A pencil")
item_3 = Item("A teapot")
old_stuff = Box(item_1, item_2)
item_manager.register_item(item_1, old_stuff)
item_manager.register_item(item_2, old_stuff)
new_stuff = Box(item_3, None)
item_manager.register_item(item_3, new_stuff)
box_with_picture = item_manager.get_box(item_2)
print box_with_picture.item_1.description
Also see SRP: an item should not know which box contains it.

Related

Can`t get QTreeView model of QSortFilterProxyModel() outside of the init function

I have an application which displays JSON-file to the QTreeView. For this I created my own QJsonTreeModel(QAbstractItemModel) class for QTreeView model
In my MainWindow.py I implemented it like this:
class MainWindow(QMainWindow):
def __init__(self, json_text: dict) -> None:
self.tree_view = QTreeView()
self.model = QJsonTreeModel()
self.tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree_view.customContextMenuRequested.connect(self.open_right_click_menu)
self.tree_view.setModel(self.model)
self.model.load(self.json_text)
def open_right_click_menu(self, position) -> None:
index = self.tree_view.selectionModel().currentIndex()
parent = index.parent()
if not index.isValid():
return
# workes fine
print(self.model.data(self.tree_view.selectedIndexes()[2], Qt.EditRole))
It works perfect but recently I needed to add search QLineEdit() field to find the input element in the tree. And for this task I found soultion with QSortFilterProxyModel(). Which I implemented like this:
class MainWindow(QMainWindow):
def __init__(self, json_text: dict) -> None:
self.tree_view = QTreeView()
self.line_edit = QLineEdit()
self.model = QJsonTreeModel()
self.model.load(self.json_text)
self.tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree_view.customContextMenuRequested.connect(self.open_right_click_menu)
self.filter_proxy_model = QSortFilterProxyModel()
self.filter_proxy_model.setSourceModel(self.model)
self.filter_proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) # Qt.CaseSensitive
self.filter_proxy_model.setRecursiveFilteringEnabled(True)
self.filter_proxy_model.setFilterKeyColumn(-1)
self.tree_view.setModel(self.filter_proxy_model)
self.line_edit.textChanged.connect(self.filter_proxy_model.setFilterRegExp)
def open_right_click_menu(self, position) -> None:
index = self.tree_view.selectionModel().currentIndex()
parent = index.parent()
if not index.isValid():
return
# not working rn. Segmentation fault
print(self.model.data(self.tree_view.selectedIndexes()[2], Qt.EditRole))
# also not working
print(self.filter_proxy_model.sourceModel().data(self.tree_view.selectedIndexes()[2], Qt.EditRole))
It works fine, the input text is searched in the tree and displayed normally, but the problem is in other place. I have implemented right click menu for my QTreeView:
self.tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree_view.customContextMenuRequested.connect(self.open_right_click_menu)
And in open_right_click_menu function I need to call self.model.data(self.tree_view.selectedIndexes()[2], Qt.EditRole) (index is correct, there are three columns) function or some another function of QJsonTreeModel() class which worked perfectly before adding QSortFilterProxyModel(). But after implementing QSortFilterProxyModel() I have got Segmentation fault when I am trying to call some function of QJsonTreeModel() class. Also I tried self.filter_proxy_model.sourceModel().data(self.tree_view.selectedIndexes()[2], Qt.EditRole) but it also causes Segmentation fault. Where am I wrong and how do I call functions of QJsonTreeModel() class properly?
I didn't find this question before I asked mine, but the problem was that I did`t map QSortFilterProxyModel back. So the solution is this:
def open_right_click_menu(self, position) -> None:
# not working version
print(self.model.data(self.tree_view.selectedIndexes()[2], Qt.EditRole))
# working version
print(self.model.data(
self.filter_proxy_model.mapToSource(self.tree_view.selectedIndexes()[2]), Qt.EditRole))

How can I embed extra column/external URLs in Flask-appbuilder list/detail model view?

Flask-appbuilder's ModelView can display list and detail for a model. Very handy and save many times for CURD operations.
Sometimes the application demands more features with extra column(s) besides CURD operations. For example, in a IoT related Device ModelView, besides CRUD, I want to link to anther realtime gauge web page, or call Web API offered by device server to send command to device.
In other Python framework, like Tornado/Cyclone, I will manually designed a template page (with extra buttons) and (embed extra) javascript code. But I am still not familiar with FAB's structure.
I can make these extra operations as external links to other exposed methods. And add these links to models as data fields. But I think these design is quite ugly. And its URL is too long to display as well.
Any better ideas? Which methods should be overriden?
I found a solution from FAB's issues site on Github. In models.py, you can define a method, then use the method in views.py. Then the resource list page will treat the method as addtional column. This solution has a drawback, you have to write HTML in a model method.
Here is my code.
models.py
class Device(Model):
id = Column(Integer, primary_key = True)
snr = Column(String(256), unique = True)
name = Column(String(128))
addr = Column(String(256))
latitude = Column(Float)
longitude = Column(Float)
status = Column(Enum('init','normal','transfer','suspend'), default = 'init')
owner_id = Column(Integer, ForeignKey('account.id'))
owner = relationship("Account")
def __repr__(self):
return self.name
def get_gauge_url(self):
btn = "<i class=\"fa fa-dashboard\">".format(self.id)
return btn
views.py
class DeviceView(ModelView):
datamodel = SQLAInterface(Device)
related_views = [PermitView, EventView]
label_columns = {'snr':'SNR',
'owner_id':'Owner',
'get_gauge_url':'Gauge'}
list_columns = ['name','snr','addr','owner','get_gauge_url']
edit_columns = ['name','snr','owner','addr','latitude','longitude','status',]
add_columns = edit_columns
show_fieldsets = [
('Summary',
{'fields':['name','snr','owner']}
),
('Device Info',
{'fields':['addr','latitude','longitude','status'],'expanded':True}
),
]

POST a list to the API, update or create depending on the existence of that instance

I have a view which allows me to post multiple entries to a model. Currently if I add all new entries, they are added successfully. But if I add any entry for which the pk already exists, it naturally throws a serializer error and nothing gets updated.
I wish to write a method which will let me post multiple entries, but automatically either update an existing one OR add a new one successfully depending the existence of that instance.
The idea of a customized ListSerialzer is the closest thing I came across to achieve this but still not clear if I can do this.
Has anyone ever implemented anything like this ?
In views.py:
def post(self,request,format=None):
data = JSONParser().parse(request)
serializer = PanelSerializer(data=data,many=True)
if serializer.is_valid():
serializer.save()
return JsonResponse({"success":"true","content":serializer.data}, status=201)
return JsonResponse({'success': "false",'errorCode':"1",'errorMessage':serializer.errors}, status=400)
in serializers.py:
class PanelSerializer(serializers.ModelSerializer):
class Meta:
model = Panel
fields = ('pId','pGuid','pName', 'pVoltage', 'pAmperage','pPermission', 'pPresent', 'pSelf','pInfo')
def create(self, validated_data):
logger.info('Information incoming_1!')
print ("Atom")
return Panel.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.pId = validated_data.get('pId', instance.pId)
instance.pGuid = validated_data.get('pId', instance.pGuid)
instance.pName = validated_data.get('pName', instance.pName)
instance.pVoltage = validated_data.get('pVoltage', instance.pVoltage)
instance.pAmperage = validated_data.get('pAmperage', instance.pAmperage)
instance.pPermission = validated_data.get('pPermission', instance.pPermission)
instance.pPresent = validated_data.get('pPresent', instance.pPresent)
instance.pSelf = validated_data.get('pSelf', instance.pSelf)
instance.pInfo = validated_data.get('pInfo', instance.pInfo)
instance.save()
return instance
This is how the code stands as of now. I believe I will mainly need to either work on the update method of my serializer or first change it to a ListSerializer and write some custom logic again in the update method.

django Autocomplete-light how to choose a specific method from a mode

I am new at django and autocomplete-light. I try to get a different fields of the model from autocomplete-light, but it always return the same field. And the reason is because def in the Model defined one field. So I created another def, but can not make autocomplete-light to call that specific def. Here is my code.
models.py:
class Item(models.Model):
...
serial_number=models.CharField(max_length=100, unique=True)
barcode=models.CharField(max_length=25, unique=True)
def __unicode__(self):
return self.serial_number
def bar(self):
return self.barcode
.......
autocomplete_light_registry.py
autocomplete_light.register(Item,
name='AutocompleteItemserial',
search_fields=['serial_number'],
)
autocomplete_light.register(Item,
name='AutocompleteItembarcode',
search_fields=['barcode'],
)
Here is the issue: when I try to get the barcodes from the autocomplete-light, it returns serial_numbers. No matter what I try to get from the Item model, it always returns the serial number. I really appreciate for the answers. Thank you.
Just in case, here is the form.py
forms.py
class ItemForm(forms.ModelForm):
widgets = {
'serial_number': autocomplete_light.TextWidget('AutocompleteItemserial'),
'barcode': autocomplete_light.TextWidget('AutocompleteItembarcode'),
}
Although this is an old post but as I just faced the same issue therefore I am sharing my solution.
The reason autocomplete is returning serial_number is because django-autocomplete-light uses the __unicode__ method of the model to show the results. In your AutocompleteItembarcode all that is being done is autocomplete-light is searching by barcode field of Item.
Try the following.
In app/autocomplete_light_registry.py
from django.utils.encoding import force_text
class ItemAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['serial_number']
model = Item
choices = Item.objects.all()
def choice_label(self, choice):
"""
Return the human-readable representation of a choice.
"""
barcode = Item.objects.get(pk=self.choice_value(choice)).barcode
return force_text(barcode)
autocomplete_light.register(ItemAutocomplete)
For more help you can have a look at the source code.

How do Scrapy from_settings and from_crawler class methods work?

I need to add the following class method to my existing pipeline
http://doc.scrapy.org/en/latest/faq.html#i-m-getting-an-error-cannot-import-name-crawler
i am not sure how to have 2 of these class methods in my class
from twisted.enterprise import adbapi
import MySQLdb.cursors
class MySQLStorePipeline(object):
"""A pipeline to store the item in a MySQL database.
This implementation uses Twisted's asynchronous database API.
"""
def __init__(self, dbpool):
self.dbpool = dbpool
#classmethod
def from_settings(cls, settings):
dbargs = dict(
host= settings['DB_HOST'],
db= settings['DB_NAME'],
user= settings['DB_USER'],
passwd= settings['DB_PASSWD'],
charset='utf8',
use_unicode=True,
)
dbpool = adbapi.ConnectionPool('MySQLdb', **dbargs)
return cls(dbpool)
def process_item(self, item, spider):
pass
From my understanding of class methods, several class methods in a python class should just be fine. It just depends on which one the caller requires. However, I have only seen from_crawler until now in scrapy pipelines. From there you can get access to the settings via crawler.settings
Are you sure that from_settings is required? I did not check all occurences, but in middleware.py priority seems to apply: If a crawler object is available and a from_crawler method exists, this is taken. Otherwise, if there is a from_settings method, that is taken. Otherwise, the raw constructor is taken.
if crawler and hasattr(mwcls, 'from_crawler'):
mw = mwcls.from_crawler(crawler)
elif hasattr(mwcls, 'from_settings'):
mw = mwcls.from_settings(settings)
else:
mw = mwcls()
I admit, I do not know if this is also the place where pipelines get created (I guess not, but there is no pipelines.py), but the implementation seems very reasonable.
So, I'd just either:
reimplement the whole method as from_crawler and only use that one
add method from_crawler and use both
The new method could look like follows (to duplicate as little code as possible):
#classmethod
def from_crawler(cls, crawler):
obj = cls.from_settings(crawler.settings)
obj.do_something_on_me_with_crawler(crawler)
return obj
Of course this depends a bit on what you need.