I use Pyqt5 to bulit an easybrowser,in the webpage,most link will built a pop-up window to show new webpages,so I add the code:def createWindow(self, QWebEnginePage_WebWindowType): but en issue was followed:new webpage can not update the url in urlbar,and also the back\forward\stop\reload button doesn't work.it Confuse me so much!
here is my code:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtWebEngineWidgets import *
import tkinter as tk
import sys
class MainWindow(QMainWindow):
# noinspection PyUnresolvedReferences
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 设置窗口标题
self.setWindowTitle('My Browser')
# 设置窗口图标
self.setWindowIcon(QIcon('icons/penguin.png'))
# 设置窗口大小900*600
self.resize(900, 600)
self.show()
# 设置浏览器
self.browser = WebEngineView()
url = 'http://www.hao123.com'
# 指定打开界面的 URL
self.browser.setUrl(QUrl(url))
# 添加浏览器到窗口中
self.setCentralWidget(self.browser)
###使用QToolBar创建导航栏,并使用QAction创建按钮
# 添加导航栏
navigation_bar = QToolBar('Navigation')
# 设定图标的大小
navigation_bar.setIconSize(QSize(16, 16))
#添加导航栏到窗口中
self.addToolBar(navigation_bar)
#QAction类提供了抽象的用户界面action,这些action可以被放置在窗口部件中
# 添加前进、后退、停止加载和刷新的按钮
back_button = QAction(QIcon('icons/back.png'), 'Back', self)
next_button = QAction(QIcon('icons/next.png'), 'Forward', self)
stop_button = QAction(QIcon('icons/cross.png'), 'stop', self)
reload_button = QAction(QIcon('icons/renew.png'), 'reload', self)
back_button.triggered.connect(self.browser.back)
next_button.triggered.connect(self.browser.forward)
stop_button.triggered.connect(self.browser.stop)
reload_button.triggered.connect(self.browser.reload)
# 将按钮添加到导航栏上
navigation_bar.addAction(back_button)
navigation_bar.addAction(next_button)
navigation_bar.addAction(stop_button)
navigation_bar.addAction(reload_button)
#添加URL地址栏
self.urlbar = QLineEdit()
# 让地址栏能响应回车按键信号
self.urlbar.returnPressed.connect(self.navigate_to_url)
navigation_bar.addSeparator()
navigation_bar.addWidget(self.urlbar)
#让浏览器相应url地址的变化
self.browser.urlChanged.connect(self.renew_urlbar)
def navigate_to_url(self):
q = QUrl(self.urlbar.text())
if q.scheme() == '':
q.setScheme('http')
self.browser.setUrl(q)
def renew_urlbar(self, q):
# # 将当前网页的链接更新到地址栏
self.urlbar.setText(q.toString())
self.urlbar.setCursorPosition(0)
class WebEngineView(QWebEngineView):
windowList = []
# 重写createwindow()
def createWindow(self, QWebEnginePage_WebWindowType):
new_webview = WebEngineView()
new_window = MainWindow()
new_window.setCentralWidget(new_webview)
new_window.show()
self.windowList.append(new_window) # 注:没有这句会崩溃!!!
return new_webview
# 创建应用
app = QApplication(sys.argv)
if __name__ == "__main__":
app = QApplication.instance()
# 创建主窗口
window=MainWindow()
# 显示窗口
window.show()
# 运行应用,并监听事件
app.exec_()
You are creating the new window correctly, but you also set a new webview for that window while it already has one.
Just return its existing browser webview instead of creating a new one.
class WebEngineView(QWebEngineView):
windowList = []
def createWindow(self, QWebEnginePage_WebWindowType):
new_window = MainWindow()
new_window.show()
self.windowList.append(new_window)
return new_window.browser
Related
I have a TreeView which is displaying items from an AbstractItemModel..
Now I wanted to add extra Filter functionality to my application, but somehow, the data is not visible in the TreeView (after calling newData()).
How does the interaction between the QAbstractItemModel and the QSortFilterProxyModel happens?
what should the QSortFilterProxyModel knows more the the setSource(QAbstractItemModel)
Here my code (copied from: https://stackoverflow.com/a/60910989/298487)
import logging
import sys
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import QSortFilterProxyModel
class DBObject:
def __init__(self, name, parent, children=None):
self.name = name
self.parent = parent
self.children = children or list()
def __repr__(self):
return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}"
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root = DBObject("root", None)
def newData(self):
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", self._root)
self._root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return 1
parentItem = parent.internalPointer()
rowCount = len(parentItem.children)
logging.info(f"rowCount({parentItem}): rowCount={rowCount}")
return rowCount
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
parentItem = item.parent
logging.info(f"parent({item}): parent={parentItem}")
if parentItem is None:
return QtCore.QModelIndex()
else:
if parentItem.parent is None:
return self.createIndex(0, 0, parentItem)
else:
return self.createIndex(parentItem.parent.children.index(parentItem), 0, parentItem)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
if row != 0 or column != 0:
return QtCore.QModelIndex()
else:
logging.info(f"index({row}, {column}, None): index={self._root}")
return self.createIndex(0, 0, self._root)
parentItem = parent.internalPointer()
if 0 <= row < len(parentItem.children):
logging.info(f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}")
return self.createIndex(row, column, parentItem.children[row])
else:
logging.info(f"index({row}, {column}, {parentItem}): index=None")
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.ItemDataRole.DisplayRole:
return item.name
else:
return None
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
return (
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsSelectable)
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setFilterKeyColumn(0)
self.setRecursiveFilteringEnabled(True)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(640, 480)
centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(centralWidget)
layout = QtWidgets.QVBoxLayout(centralWidget)
self._treeView = QtWidgets.QTreeView(self)
layout.addWidget(self._treeView)
self._model = Model()
self._proxyModel = ProxyModel()
self._proxyModel.setSourceModel(self._model)
# this line will not work
self._treeView.setModel(self._proxyModel)
# if i replace it with this line, it is working
# but the filtering will not work
self._treeView.setModel(self._model)
self._proxyModel.setFilterFixedString("bar1")
button = QtWidgets.QPushButton("Add")
layout.addWidget(button)
button.clicked.connect(self._Clicked)
def _Clicked(self):
self._model.newData()
self._treeView.expandAll()
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec()
if __name__ == "__main__":
main()
I am trying to scrape this page: https://simple.ripley.com.pe/laptop-lenovo-ideapad-5-amd-ryzen-7-16gb-ram-256gb-ssd-14-2004286061746p?s=o
All ok, but I am not able to get the values in this xpath:
//*[#id="panel-Especificaciones"]/div/div/table/tbody/tr[19]/td[2]
I think it loads dynamically. It's a table with many rows inside. I would like to get those values.
Image: page section i can't scrape
This is my spider code:
import scrapy
from scrapy_splash import SplashRequest
from numpy import nan
LUA_SCRIPT = """
function main(splash)
splash.private_mode_enabled = false
splash:go(splash.args.url)
splash:wait(2)
html = splash:html()
splash.private_mode_enabled = true
return html
end
"""
class RipleySpider(scrapy.Spider):
name = "ripley"
def start_requests(self):
url = 'https://simple.ripley.com.pe/tecnologia/computacion/laptops?facet%5B%5D=Procesador%3AIntel+Core+i7'
yield SplashRequest(url=url, callback=self.parse)
def parse(self, response):
for link in response.xpath("//div[#class='catalog-container']/div/a/#href"):
yield response.follow(link.get(), callback=self.parse_products)
# for href in response.xpath("//ul[#class='pagination']/li[last()]/a/#href").getall():
# yield SplashRequest(response.urljoin(href), callback=self.parse)
def parse_products(self, response):
titulo = response.css("h1::text").get()
link = response.request.url
sku = response.css(".sku-value::text").get()
precio = response.css(".product-price::text").getall()
if len(precio)==1:
precio_normal = nan
precio_internet = precio[0]
precio_tarjeta_ripley = nan
elif len(precio)==2:
precio_normal = precio[0]
precio_internet = precio[1]
precio_tarjeta_ripley = nan
elif len(precio)==4:
precio_normal = precio[0]
precio_internet = precio[1]
precio_tarjeta_ripley = precio[-1]
try:
# descripcion = response.css(".product-short-description::text").get()
descripcion = response.xpath('//*[#id="panel-Especificaciones"]/div/div/table/tbody/tr[1]/td[2]/text()').get()
except:
descripcion = 'sin valor'
yield {
'Título': titulo,
'Link': link,
'SKU': sku,
'Precio Normal': precio_normal,
'Precio Internet': precio_internet,
'Precio Tarjeta Ripley': precio_tarjeta_ripley,
'Descripción': descripcion,
}
Please, what solutions does scrapy offer? Thanks in advance for your help.
P.D.: I'm using Docker with Splash in localhost:8050. settings.py according to documentation.
I have a Django 3.2, python 3.6 website. I am having issues uploading multiple files to a model that also has a M2M field. I get an error in the save_related method at the indicated line:
ValueError: "<Image: title>" needs to have a value for field "image_id" before this many-to-many relationship can be used.
I have used this same method to upload multiple files to models without an M2M field, so I am not sure where I am going wrong.
models.py
class Tags(models.Model):
tag_id = models.AutoField(primary_key=True)
tag_name = models.CharField(max_length=255)
class Image(models.Model):
image_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.TextField()
original_image = models.ImageField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
exif_data = models.JSONField(default=dict)
computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")
tags = models.ManyToManyField(Tags, blank=True)
admin.py
class ImageForm(forms.ModelForm):
original_image = forms.ImageField(widget=forms.FileInput(attrs={'multiple': True}))
class ImageAdmin(admin.ModelAdmin):
form = ImageForm
class Meta:
model = Image
fields = '__all__'
def save_related(self, request, form, *args, **kwargs):
tags = form.cleaned_data.pop('tags', ())
image = form.instance
for tag in tags:
image.tags.add(tag) # error occurs here
super(ImageAdmin, self).save_related(request, form, *args, **kwargs)
def save_model(self, request, obj, form, change):
if form.is_valid():
if not change:
# Uploading one or more images))
files = request.FILES.getlist('original_image')
for f in files:
image = Image()
if "Title" not in form.cleaned_data:
form.cleaned_data['Title'] = clean_title(f.name)
image.computed_sha256 = image_processing_utils.compute_sha256(f)
image.original_image = f
image.description = form.cleaned_data['description']
image.exif_data = image_processing_utils.read_exif_data(f)
image.save()
else:
pass
I could not find a way to upload multiple files to a model with a M2M field, so I punted and took the M2M field out of the model.
models.py
class Image(models.Model):
image_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=True)
description = models.TextField()
original_image = models.FileField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
exif_data = models.JSONField(default=dict)
computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")
def __str__(self):
return self.title
class Meta:
db_table = 'Image'
class ImageTags(models.Model):
image_id = models.ForeignKey(Image, on_delete=models.CASCADE)
tag_id = models.ForeignKey(Tags, on_delete=models.CASCADE)
class Meta:
db_table = 'ImageTags'
admin.py
class ImageAdminForm(forms.ModelForm):
original_image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
tag_choices = Tags.objects.values_list('tag_id', 'tag_name')
self.fields['tags'] = forms.MultipleChoiceField(choices=tag_choices, widget=forms.SelectMultiple, required=False)
class ImageAdmin(admin.ModelAdmin):
list_display = ('image_id', 'title', 'description', 'views', 'original_image', 'get_tags', 'exif_data', 'created', 'updated')
readonly_fields = ('thumb_image', 'album_image', 'display_image', 'exif_data', 'views', )
form = ImageAdminForm
class Meta:
model = Image
fields = '__all__'
fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('title', 'description', 'original_image',)
}),
)
def get_form(self, request, obj=None, **kwargs):
# https://stackoverflow.com/questions/1057252/how-do-i-access-the-request-object-or-any-other-variable-in-a-forms-clean-met
logger.debug("get_form START")
kwargs['fields'] = flatten_fieldsets(self.fieldsets)
form = super(ImageAdmin, self).get_form(request, obj, **kwargs)
form.request_obj = request
logger.debug("get_form END")
return form
def get_fieldsets(self, request, obj=None):
logger.debug("get_fieldsets START")
import copy
fieldsets = copy.deepcopy(super(ImageAdmin, self).get_fieldsets(request, obj))
logger.debug("1 fieldsets=%s" % fieldsets)
change_page_fieldset = list(fieldsets[0][1]['fields'])
logger.debug("1 change_page_fieldset=%s" % change_page_fieldset)
#if obj:
if 'tags' not in change_page_fieldset:
change_page_fieldset.append('tags')
logger.debug('2 change_page_fieldset=%s' % change_page_fieldset)
fieldsets[0][1]['fields'] = tuple(change_page_fieldset)
logger.debug('2 fieldsets=%s' % fieldsets)
return fieldsets
def get_tags(self, obj):
tag_ids = list(ImageTags.objects.filter(image_id=obj).values_list("tag_id", flat=True))
tag_names = list(Tags.objects.filter(tag_id__in=tag_ids).values_list('tag_name', flat=True))
return ", ".join([t for t in tag_names])
def save_model(self, request, obj, form, change):
logger.debug("save_model START")
logger.debug("obj=%s, change=%s, valid=%s" % (obj, change, form.is_valid()))
logger.debug("changed fields=%s" % form.changed_data)
logger.debug("obj.original_image=%s" % obj.original_image)
if utils.is_celery_working():
if form.is_valid():
if not change:
# Uploading one or more images
logger.debug("\tvalid form")
logger.debug("form.cleaned_data=%s",form.cleaned_data)
logger.debug("files=%s" % request.FILES.getlist('original_image'))
files = request.FILES.getlist('original_image')
for f in files:
image = Image()
if not form.cleaned_data['title']:
image.title = clean_title(f.name)
else:
image.title = form.cleaned_data['title']
logger.debug("form.cleaned_data['title']=%s" % form.cleaned_data['title'])
logger.debug("f=%s" % f)
image.original_image = f
image.description = form.cleaned_data['description']
image.save()
# save the tags
tags = form.cleaned_data['tags']
for tag in tags:
ImageTags.objects.create(tag_id_id=int(tag), image_id_id=image.pk)
#super().save_model(request, obj, form, change)
else:
# processing a change form, so redo all the fields
pass
#super().save_model(request, obj, form, change)
else:
# error - form is invalid
pass
else:
# error - celery not working
pass
logger.debug("save_model END")
'''
Does not appear the buttons inside recycleview
'''
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.bx = RecycleBoxLayout(default_size=(None, dp(56)), default_size_hint=(1, None),
size_hint=(1, None), orientation='vertical',)
self.but = Button(text= 'hola')
self.bx.add_widget(self.but)
self.bx.bind(minimum_height=self.bx.setter("height"))
self.data = [{'text': str(x)} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
The class RecycleView uses the attribute viewclass as data container, so you have to use self.viewclass = Button here.
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.bx = RecycleBoxLayout(
default_size=(None, dp(56)),
default_size_hint=(1, None),
size_hint=(1, None),
orientation='vertical',
)
self.bx.bind(minimum_height=self.bx.setter("height"))
self.add_widget(self.bx)
Clock.schedule_once(self.update_view)
def update_view(self, *args):
#Items that will be used as data-container.
self.viewclass = Button # Or, "Button"
self.data = [{'text': str(x)} for x in range(100)]
Also note that you've to schedule the data updation in order to get the view. Alternatively, you can define (almost) everything in kivy-lang without the need of scheduling. You can find an example in Kivy documentation.
I am new to CherryPy. I am using the default dispatcher, with a URL structure similar to this:
root = Root()
root.page1 = Page1()
root.page1.apple = Apple()
root.page2 = Page2()
root.page2.orange = Orange()
Orange renders a template, in which I need a link to Apple. I could just hardcode /page1/apple/. But how can I get the URL of Apple in a DRY manner?
Can this be done with the default dispatcher in CherryPy, or is it only possible with the Routes dispatcher?
(I am coming from the Django world, where one would use reverse() for this purpose.)
You can access to the mounted apps through
cherrypy.tree.apps[mount_point].root
root is always the mounted instance to the mount point. So a reverse function would look like:
def reverse(cls):
# get link to a class type
for app_url in cherrypy.tree.apps.keys():
if isinstance(cherrypy.tree.apps[app_url].root, cls):
# NOTE: it will return with the first mount point of this class
return app_url
Please find a sample code below that uses your classes. http://localhost:8080/page4/orange/ prints out { Orange and the link to apple: : "/page3/apple" }
import cherrypy
link_to_apple_global = ''
class Orange(object):
def __init__(self):
pass
#cherrypy.expose
#cherrypy.tools.json_out()
def index(self):
return {"Orange and the link to apple: ": link_to_apple_global}
class Page2(object):
def __init__(self):
pass
#cherrypy.expose
def index(self):
return "Page2"
class Apple(object):
def __init__(self):
pass
#cherrypy.expose
def index(self):
return "Apple"
class Page1(object):
def __init__(self):
pass
#cherrypy.expose
def index(self):
return "Page1"
class Root(object):
def __init__(self):
pass
#cherrypy.expose
def index(self):
return "Root"
def reverse(cls):
#return cherrypy.tree.apps.keys()
#return dir(cherrypy.tree.apps[''].root)
#return dir(cherrypy.tree.apps['/page3/apple'].root)
# get link to apple
for app_url in cherrypy.tree.apps.keys():
if isinstance(cherrypy.tree.apps[app_url].root, cls):
# NOTE: it will return with the first instance
return app_url
root = Root()
root.page1 = Page1()
root.page1.apple = Apple()
root.page2 = Page2()
root.page2.orange = Orange()
cherrypy.tree.mount(root, '/')
# if you do not specify the mount points you can find the objects
# in cherrypy.tree.apps[''].root...
cherrypy.tree.mount(root.page1, '/page4')
cherrypy.tree.mount(root.page2, '/page3')
cherrypy.tree.mount(root.page2.orange, '/page4/orange')
cherrypy.tree.mount(root.page1.apple, '/page3/apple')
link_to_apple_global = reverse(Apple)
cherrypy.engine.start()
cherrypy.engine.block()